Skip to content

Commit

Permalink
sign: GPG backend tests
Browse files Browse the repository at this point in the history
  • Loading branch information
julienvincent committed Feb 20, 2024
1 parent 0efaef2 commit 7c11a61
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 1 deletion.
18 changes: 18 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ jobs:

steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

# The default version of gpg installed on the runners is a version baked in with git
# which only contains the components needed by git and doesn't work for our test cases.
#
# This installs the latest gpg4win version, which is a variation of GnuPG built for
# Windows.
#
# There is some issue with windows PATH max length which is what all the PATH wrangling
# below is for. Please see the below link for where this fix was derived from:
# https://github.com/orgs/community/discussions/24933
- name: Setup GnuPG [windows]
if: ${{ matrix.os == 'windows-latest' }}
run: |
$env:PATH = "C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\chocolatey\bin"
[Environment]::SetEnvironmentVariable("Path", $env:PATH, "Machine")
choco install --yes gpg4win
echo "C:\Program Files (x86)\Gpg4win\..\GnuPG\bin" >> $env:GITHUB_PATH
- name: Install Rust
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
Expand Down
6 changes: 5 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
in
{
packages = {
jujutsu = ourRustPlatform.buildRustPackage rec {
jujutsu = ourRustPlatform.buildRustPackage {
pname = "jujutsu";
version = "unstable-${self.shortRev or "dirty"}";

Expand All @@ -82,6 +82,7 @@
installShellFiles
makeWrapper
pkg-config
gnupg # for signing tests
] ++ linuxNativeDeps;
buildInputs = with pkgs; [
openssl zstd libgit2 libssh2
Expand Down Expand Up @@ -144,6 +145,9 @@
# In case you need to run `cargo run --bin gen-protos`
protobuf

# To run the signing tests
gnupg

# For building the documentation website
poetry
] ++ darwinDeps ++ linuxNativeDeps;
Expand Down
1 change: 1 addition & 0 deletions 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 Down
186 changes: 186 additions & 0 deletions lib/tests/test_gpg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#[cfg(unix)]
use std::fs::Permissions;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use std::process::Stdio;

use assert_matches::assert_matches;
use insta::assert_debug_snapshot;
use jj_lib::gpg_signing::GpgBackend;
use jj_lib::signing::{SigStatus, SignError, SigningBackend};

static PRIVATE_KEY: &str = r#"-----BEGIN PGP PRIVATE KEY BLOCK-----
lFgEZWI3pBYJKwYBBAHaRw8BAQdAaPLTNADvDWapjAPlxaUnx3HXQNIlwSz4EZrW
3Z7hxSwAAP9liwHZWJCGI2xW+XNqMT36qpIvoRcd5YPaKYwvnlkG1w+UtDNTb21l
b25lIChqaiB0ZXN0IHNpZ25pbmcga2V5KSA8c29tZW9uZUBleGFtcGxlLmNvbT6I
kwQTFgoAOxYhBKWOXukGcVPI9eXp6WOHhcsW/qBhBQJlYjekAhsDBQsJCAcCAiIC
BhUKCQgLAgQWAgMBAh4HAheAAAoJEGOHhcsW/qBhyBgBAMph1HkBkKlrZmsun+3i
kTEaOsWmaW/D6NEdMFiw0S/jAP9G3jOYGiZbUN3dWWB2246Oi7SaMTX8Xb2BrLP2
axCbC5RYBGVjxv8WCSsGAQQB2kcPAQEHQE8Oa4ahtVG29gIRssPxjqF4utn8iHPz
m5z/8lX/nl3eAAD5AZ6H2pNhiy2gnGkbPLHw3ZyY4d0NXzCa7qc9EXqOj+sRrLQ9
U29tZW9uZSBFbHNlIChqaiB0ZXN0IHNpZ25pbmcga2V5KSA8c29tZW9uZS1lbHNl
QGV4YW1wbGUuY29tPoiTBBMWCgA7FiEER1BAaEpU3TKUiUvFTtVW6XKeAA8FAmVj
xv8CGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQTtVW6XKeAA/6TQEA
2DkPm3LmH8uG6qLirtf62kbG7T+qljIsarQKFw3CGakA/AveCtrL7wVSpINiu1Rz
lBqJFFP2PqzT0CRfh94HSIMM
=6JC8
-----END PGP PRIVATE KEY BLOCK-----
"#;

struct GpgEnvironment {
homedir: tempfile::TempDir,
}

impl GpgEnvironment {
fn new() -> Result<Self, std::process::Output> {
let dir = tempfile::Builder::new()
.prefix("jj-gpg-signing-test-")
.tempdir()
.unwrap();

let path = dir.path();

#[cfg(unix)]
std::fs::set_permissions(path, Permissions::from_mode(0o700)).unwrap();

let mut gpg = std::process::Command::new("gpg")
.arg("--homedir")
.arg(path)
.arg("--import")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();

gpg.stdin
.as_mut()
.unwrap()
.write_all(PRIVATE_KEY.as_bytes())
.unwrap();

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

let res = gpg.wait_with_output().unwrap();

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

Ok(GpgEnvironment { homedir: dir })
}
}

fn backend(env: &GpgEnvironment) -> GpgBackend {
// don't really need faked time for current tests,
// but probably will need it for end-to-end cli tests
GpgBackend::new("gpg".into(), false).with_extra_args(&[
"--homedir".into(),
env.homedir.path().as_os_str().into(),
"--faked-system-time=1701042000!".into(),
])
}

#[test]
fn gpg_singing_roundtrip() {
let env = GpgEnvironment::new().unwrap();
let backend = backend(&env);
let data = b"hello world";
let signature = backend.sign(data, None).unwrap();

let check = backend.verify(data, &signature).unwrap();
assert_eq!(check.status, SigStatus::Good);
assert_eq!(check.key.unwrap(), "638785CB16FEA061");
assert_eq!(
check.display.unwrap(),
"Someone (jj test signing key) <[email protected]>"
);

let check = backend.verify(b"so so bad", &signature).unwrap();
assert_eq!(check.status, SigStatus::Bad);
assert_eq!(check.key.unwrap(), "638785CB16FEA061");
assert_eq!(
check.display.unwrap(),
"Someone (jj test signing key) <[email protected]>"
);
}

#[test]
fn gpg_signing_roundtrip_explicit_key() {
let env = GpgEnvironment::new().unwrap();
let backend = backend(&env);
let data = b"hello world";
let signature = backend.sign(data, Some("Someone Else")).unwrap();

assert_debug_snapshot!(backend.verify(data, &signature).unwrap(), @r###"
Verification {
status: Good,
key: Some(
"4ED556E9729E000F",
),
display: Some(
"Someone Else (jj test signing key) <[email protected]>",
),
}
"###);
assert_debug_snapshot!(backend.verify(b"so so bad", &signature).unwrap(), @r###"
Verification {
status: Bad,
key: Some(
"4ED556E9729E000F",
),
display: Some(
"Someone Else (jj test signing key) <[email protected]>",
),
}
"###);
}

#[test]
fn unknown_key() {
let env = GpgEnvironment::new().unwrap();
let backend = backend(&env);
let signature = br"-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQQs238pU7eC/ROoPJ0HH+PjJN1zMwUCZWPa5AAKCRAHH+PjJN1z
MyylAP9WQ3sZdbC4b1C+/nxs+Wl+rfwzeQWGbdcsBMyDABcpmgD/U+4KdO7eZj/I
e+U6bvqw3pOBoI53Th35drQ0qPI+jAE=
=kwsk
-----END PGP SIGNATURE-----";
assert_debug_snapshot!(backend.verify(b"hello world", signature).unwrap(), @r###"
Verification {
status: Unknown,
key: Some(
"071FE3E324DD7333",
),
display: None,
}
"###);
assert_debug_snapshot!(backend.verify(b"so bad", signature).unwrap(), @r###"
Verification {
status: Unknown,
key: Some(
"071FE3E324DD7333",
),
display: None,
}
"###);
}

#[test]
fn invalid_signature() {
let env = GpgEnvironment::new().unwrap();
let backend = backend(&env);
let signature = br"-----BEGIN PGP SIGNATURE-----
super duper invalid
-----END PGP SIGNATURE-----";
assert_matches!(
backend.verify(b"hello world", signature),
Err(SignError::InvalidSignatureFormat)
);
}

0 comments on commit 7c11a61

Please sign in to comment.