use std::fs; use sequoia_openpgp as openpgp; use openpgp::serialize::Serialize; mod common; use common::Environment; fn create_environment() -> anyhow::Result<(Environment, String)> { Environment::scooby_gang_bootstrap() } #[test] fn sign_commit() -> anyhow::Result<()> { let (e, root) = create_environment()?; let p = e.git_state(); // Bookmark. e.git(&["checkout", "-b", "test-base"])?; // Willow's code-signing key can change the source code, as she // has the sign-commit right. e.git(&["checkout", "-b", "test-willow"])?; fs::write(p.join("a"), "Aller Anfang ist schwer.")?; e.git(&["add", "a"])?; e.git(&[ "commit", "-m", "First change.", &format!("-S{}", e.willow.fingerprint), ])?; e.sq_git(&["log", "--trust-root", &root])?; // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Her release key also has that right, because she needs it in // order to give it to new users. e.git(&["checkout", "-b", "test-willow-release"])?; fs::write(p.join("a"), "Aller Anfang ist schwer. -- Schiller")?; e.git(&["add", "a"])?; e.git(&[ "commit", "-m", "Someone is not quite correct on the internet.", &format!("-S{}", e.willow_release.fingerprint), ])?; e.sq_git(&["log", "--trust-root", &root])?; // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Buffy's cert was not yet added, so she may not sign commits. e.git(&["checkout", "-b", "test-buffy"])?; fs::write(p.join("a"), "Aller Anfang ist schwer, unless you are super strong!1")?; e.git(&["add", "a"])?; e.git(&[ "commit", "-m", "Well, actually...", &format!("-S{}", e.buffy.fingerprint), ])?; let commit_2 = e.git_current_commit()?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); let commit_3 = e.git_commit(&[("workwork.txt", Some(b"Hiho, hiho"))], "Off to work we go...", Some(&e.willow_release)).unwrap(); // commit 2 is still bad. assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // But if we use the bad commit as the trust root, it should work, // because we don't check that the trust root is authenticated by // its parent, and commit 2 authenticates commit 3. assert!(e.sq_git(&["log", "--trust-root", &commit_2]).is_ok()); // Let's check the range commit_2..commit_3, but use the root as // our trust root instead of commit_2. This should again fail. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", commit_2, commit_3)]).is_err()); // commit_2..commit_2 is empty. But, we should still check for a // path from the trust root to commit_2. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", commit_2, commit_2)]).is_err()); // commit_3..commit_3 is empty. But, we should still check for a // path from the commit_2 to commit_3. assert!(e.sq_git(&["log", "--trust-root", &commit_2, &format!("{}..{}", commit_3, commit_3)]).is_ok()); Ok(()) } #[test] fn add_user() -> anyhow::Result<()> { let (e, root) = create_environment()?; // Bookmark. e.git(&["checkout", "-b", "test-base"])?; // Willow's code-signing key can change the source code, but she // can not add users. e.git(&["checkout", "-b", "test-willow"])?; // Try to add Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--sign-commit" ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.willow.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // However, her release key does have that right. e.git(&["checkout", "-b", "test-willow-release"])?; // Try to add Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--sign-commit" ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.willow_release.fingerprint), ])?; e.sq_git(&["log", "--trust-root", &root])?; // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Xander's cert was not yet added, so he definitely may not add // users either. e.git(&["checkout", "-b", "test-xander"])?; // Try to add Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--sign-commit" ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.xander.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); Ok(()) } #[test] fn retire_user() -> anyhow::Result<()> { let (e, root) = create_environment()?; // Add Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--sign-commit" ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.willow_release.fingerprint), ])?; e.sq_git(&["log", "--trust-root", &root])?; // Bookmark. e.git(&["checkout", "-b", "test-base"])?; // Willow's code signing key may not retire Buffy. e.git(&["checkout", "-b", "test-willow"])?; // Try to retire Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--no-sign-commit", ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.willow.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // However, her release key does have that right. e.git(&["checkout", "-b", "test-willow-release"])?; // Try to retire Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--no-sign-commit", ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.willow_release.fingerprint), ])?; e.sq_git(&["log", "--trust-root", &root])?; // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Xander's cert was not yet added, so he definitely may not // retire users either. e.git(&["checkout", "-b", "test-xander"])?; // Try to retire Buffy. e.sq_git(&[ "policy", "authorize", e.buffy.petname, &e.buffy.fingerprint.to_string(), "--no-sign-commit", ])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Add Buffy.", &format!("-S{}", e.xander.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); Ok(()) } #[test] fn audit() -> anyhow::Result<()> { // Introduce some bad commits, and try to recover. // // When we use in-band policies, then only a certificate with the // audit capability can goodlist commits, and can it can only // recover from hard revocations. // // When we use an external policy (using --policy-file), we can't // see who added a goodlist entry. But that doesn't matter // because the policy is fully trusted. An external policy can // recover from any type of veritifcation failure include the // complete absence of a signature. let (e, root) = create_environment()?; let p = e.git_state(); // Add a commit from Willow, which is allowed. This makes sure // the trust root's policy is not applied to the first dodgy commit. fs::write(p.join("superpowers"), "Willow can break encryption.")?; e.git(&["add", "superpowers"])?; e.git(&[ "commit", "-m", "Willow data commit.", &format!("-S{}", e.willow.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_ok()); // Add a commit signed by Xander, who is not authorized to do // that. fs::write(p.join("a"), "Aller Anfang ist schwer, I'll go fetch the hammer!")?; e.git(&["add", "a"])?; e.git(&[ "commit", "-m", "No problem, we'll get you up and running in no time.", &format!("-S{}", e.xander.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml"]).is_err()); let bad_commit = e.git_current_commit()?; // Bookmark. e.git(&["checkout", "-b", "test-base"])?; // Now we try to recover by good-listing the bad commit. // Willow's code signing key may not goodlist commits. e.git(&["checkout", "-b", "test-willow"])?; // Try to goodlist the commit. e.sq_git(&["policy", "goodlist", &bad_commit])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Goodlist the bad commit.", &format!("-S{}", e.willow.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // When specified via an external policy, this is enough. assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml"]).is_ok()); // But, if we look further back, the commit is now goodlisted, but // the signer that got the commit goodlisted does not have the // audit right. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // When specified via an external policy, this is enough. assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml", &format!("{}..", root)]).is_ok()); // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Willow's release key may goodlist commits. e.git(&["checkout", "-b", "test-willow-release"])?; // Try to goodlist the commit. e.sq_git(&["policy", "goodlist", &bad_commit])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Goodlist the bad commit.", &format!("-S{}", e.willow_release.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // When specified via an external policy, this is enough. assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml"]).is_ok()); // But, if we look further back, the commit is now goodlisted, but // it is still considered bad, because in-band goodlisting can // only be used to recover from signing keys that have been hard // revoked. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Goodlisting this commit from an external policy is enough. e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml", &format!("{}..", root)])?; // Reset. e.git(&["checkout", "test-base"])?; e.git(&["clean", "-fdx"])?; // Xander's cert was not yet added, so he definitely may not // goodlist his own commit. e.git(&["checkout", "-b", "test-xander"])?; // Try to goodlist the commit. e.sq_git(&["policy", "goodlist", &bad_commit])?; e.git(&["add", "openpgp-policy.toml"])?; e.git(&[ "commit", "-m", "Goodlist the bad commit.", &format!("-S{}", e.xander.fingerprint), ])?; assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml"]).is_err()); // But, if we look further back, the commit is now goodlisted, but // the signer that got the commit goodlisted does not have the // audit right. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Goodlisting this commit from an external policy is enough. assert!(e.sq_git(&["log", "--trust-root", &root, "--policy-file", "openpgp-policy.toml", &format!("{}..", root)]).is_err()); Ok(()) } #[test] #[allow(unused_variables)] fn goodlist_1() -> anyhow::Result<()> { let (e, root) = create_environment()?; // G <- target // | // F <- Good list B // | // E // | // D <- Add revocation for Riley // | // C // | // B <- Signature by Riley // | // A <- Trust root. // Add Riley as a committer. e.sq_git(&[ "policy", "authorize", e.riley.petname, &e.riley.fingerprint.to_string(), "--sign-commit", ])?; let commit_a = e.git_commit(&[("openpgp-policy.toml", None)], "A: willow authorizes riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("riley.txt", Some(b"riley was here"))], "B: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("workwork.txt", Some(b"1"))], "C: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Willow adds a hard revocation for Riley, but does not goodlist // his commit. let riley_revocation_pgp = e.scratch_state().join("riley-revocation.pgp"); let mut f = std::fs::File::create(&riley_revocation_pgp).unwrap(); e.riley.hard_revoke().serialize(&mut f).unwrap(); drop(f); e.sq_git(&[ "policy", "authorize", "--cert-file", &riley_revocation_pgp.to_str().unwrap(), e.riley.petname, ])?; let commit_d = e.git_commit(&[("openpgp-policy.toml", None)], "D: willow imports hard revocation for riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_e = e.git_commit(&[("workwork.txt", Some(b"e"))], "E: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); e.sq_git(&[ "policy", "goodlist", &commit_b, ])?; let commit_f = e.git_commit(&[("openpgp-policy.toml", None)], "F: willow good lists riley's commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_g = e.git_commit(&[("workwork.txt", Some(b"g"))], "G: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); Ok(()) } #[test] #[allow(unused_variables)] fn goodlist_2() -> anyhow::Result<()> { let (e, root) = create_environment()?; // K <- target // / \ // | I <- Good list B // | | // | H // | | // J G // | | // | F // | | // | E // \ / // D <- Add revocation for Riley // | // C // | // B <- Signature by Riley // | // A <- Trust root. // // By inspection, we see that there is an authenticated path from // A to K via I, because I goodlists B. That path is longer than // the path via J. If we do a breath-first walk from K to A, then // we'll visit the nodes in the following order: K, J, I, D, H, C, // G, B... We'll reject B, because the good list hasn't // propagated to B. And the goodlist will never propagate to B, // because we'd have to visit D, C, and B a second time. // // We avoid this problem by doing a topographical walk. That is, // we don't visit D until we've visit all of its children (J and // E). Then, all good lists have propagated to D and when we // visit B, B is on the goodlist. // Add Riley as a committer. e.sq_git(&[ "policy", "authorize", e.riley.petname, &e.riley.fingerprint.to_string(), "--sign-commit", ])?; let commit_a = e.git_commit(&[("openpgp-policy.toml", None)], "A: willow authorizes riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("riley.txt", Some(b"riley was here"))], "B: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("workwork.txt", Some(b"1"))], "C: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Willow adds a hard revocation for Riley, but does not goodlist // his commit. let riley_revocation_pgp = e.scratch_state().join("riley-revocation.pgp"); let mut f = std::fs::File::create(&riley_revocation_pgp).unwrap(); e.riley.hard_revoke().serialize(&mut f).unwrap(); drop(f); e.sq_git(&[ "policy", "authorize", "--cert-file", &riley_revocation_pgp.to_str().unwrap(), e.riley.petname, ])?; let commit_d = e.git_commit(&[("openpgp-policy.toml", None)], "D: willow imports hard revocation for riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_e = e.git_commit(&[("workwork.txt", Some(b"e"))], "E: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_f = e.git_commit(&[("workwork.txt", Some(b"f"))], "F: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_g = e.git_commit(&[("workwork.txt", Some(b"g"))], "G: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_h = e.git_commit(&[("workwork.txt", Some(b"h"))], "H: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); e.sq_git(&[ "policy", "goodlist", &commit_b, ])?; let commit_i = e.git_commit(&[("openpgp-policy.toml", None)], "I: willow good lists riley's commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Reset to d. e.git(&["checkout", &commit_d])?; e.git(&["clean", "-fdx"])?; let commit_j = e.git_commit(&[("workwork.md", Some(b"1"))], "J: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Merge I and J. e.git(&[ "merge", "-m", "K: Merge I and J", &format!("-S{}", e.willow_release.fingerprint), &commit_j, &commit_i])?; let commit_k = e.git_current_commit()?; assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); Ok(()) } #[test] #[allow(unused_variables)] fn goodlist_3() -> anyhow::Result<()> { let (e, root) = create_environment()?; // F <- target // / \ // | D // | | // E | <- Add revocation for Riley // | | // | C // \ / // B <- Signature by Riley // | // A <- Trust root. // // // We shouldn't be able to verify F, because even though A - B - C // - D - F taken alone is an authentic path, we should notice the // revocation certificate added in E and reject B. // Add Riley as a committer. e.sq_git(&[ "policy", "authorize", e.riley.petname, &e.riley.fingerprint.to_string(), "--sign-commit", ])?; let commit_a = e.git_commit(&[("openpgp-policy.toml", None)], "A: willow authorizes riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("riley.txt", Some(b"riley was here"))], "B: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("workwork.txt", Some(b"c"))], "C: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_d = e.git_commit(&[("workwork.txt", Some(b"d"))], "D: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Reset to b. e.git(&["checkout", &commit_b])?; e.git(&["clean", "-fdx"])?; // Willow adds a hard revocation for Riley, but does not goodlist // his commit. let riley_revocation_pgp = e.scratch_state().join("riley-revocation.pgp"); let mut f = std::fs::File::create(&riley_revocation_pgp).unwrap(); e.riley.hard_revoke().serialize(&mut f).unwrap(); drop(f); e.sq_git(&[ "policy", "authorize", "--cert-file", &riley_revocation_pgp.to_str().unwrap(), e.riley.petname, ])?; let commit_e = e.git_commit(&[("openpgp-policy.toml", None)], "E: willow imports hard revocation for riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", root, commit_d)]).is_ok()); // Merge D and E. e.git(&[ "merge", "-m", "F: Merge D and E", &format!("-S{}", e.willow_release.fingerprint), &commit_d, &commit_e])?; let commit_k = e.git_current_commit()?; assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); Ok(()) } #[test] #[allow(unused_variables)] fn goodlist_4() -> anyhow::Result<()> { let (e, root) = create_environment()?; // G <- target // / \ // F | <- Good list C // | | // | E <- Good list B // \ / // D <- Add revocation for Riley. // | // C <- Signature by Riley // | // B <- Signature by Riley // | // A <- Trust root. // // // Along, neither A-B-C-D-E-G or A-B-C-D-F-G is valid. But taken // together, when visiting C, there is an authenticated suffix // that goodlists C, and when visiting B, there is also an // authenticates suffix that authenticates B. So, G is // authenticated! // Add Riley as a committer. e.sq_git(&[ "policy", "authorize", e.riley.petname, &e.riley.fingerprint.to_string(), "--sign-commit", ])?; let commit_a = e.git_commit(&[("openpgp-policy.toml", None)], "A: willow authorizes riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("riley.txt", Some(b"riley was here"))], "B: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("riley.txt", Some(b"riley was here, again"))], "C: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Willow adds a hard revocation for Riley, but does not goodlist // his commit. let riley_revocation_pgp = e.scratch_state().join("riley-revocation.pgp"); let mut f = std::fs::File::create(&riley_revocation_pgp).unwrap(); e.riley.hard_revoke().serialize(&mut f).unwrap(); drop(f); e.sq_git(&[ "policy", "authorize", "--cert-file", &riley_revocation_pgp.to_str().unwrap(), e.riley.petname, ])?; let commit_d = e.git_commit(&[("openpgp-policy.toml", None)], "D: willow imports hard revocation for riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); e.sq_git(&[ "policy", "goodlist", &commit_b, ])?; let commit_e = e.git_commit(&[("openpgp-policy.toml", None)], "E: willow good lists riley's commit (B)", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Reset to d. e.git(&["checkout", &commit_d])?; e.git(&["clean", "-fdx"])?; e.sq_git(&[ "policy", "goodlist", &commit_c, ])?; let commit_f = e.git_commit(&[("openpgp-policy.toml", None)], "F: willow good lists riley's commit (C)", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Merge E and F. // // This is a bit complicated, because the two good list entries // are going to conflict. Create a merge commit (but don't commit // it) using the content of F. Then manually merge in E (which // goodlisted B). e.git(&[ "merge", "-m", "G: Merge E and F", "-s", "ours", "--no-commit", &commit_e, &commit_f])?; e.sq_git(&[ "policy", "goodlist", &commit_b, ])?; let commit_f = e.git_commit(&[("openpgp-policy.toml", None)], "G: Merge E and F", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); Ok(()) } #[test] #[allow(unused_variables)] fn goodlist_5() -> anyhow::Result<()> { let (e, root) = create_environment()?; // G <- target // | // F <- Xander goodlists B, but that's not allowed // | // E // | // D <- Add revocation for Riley // | // C // | // B <- Signature by Riley // | // A <- Trust root. // // Make sure that goodlisting that is not allowed is, in fact, // ignored. // Add Riley as a committer. e.sq_git(&[ "policy", "authorize", e.riley.petname, &e.riley.fingerprint.to_string(), "--sign-commit", ])?; let commit_a = e.git_commit(&[("openpgp-policy.toml", None)], "A: willow authorizes riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("riley.txt", Some(b"riley was here"))], "B: riley signs a commit", Some(&e.riley)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("workwork.txt", Some(b"1"))], "C: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Willow adds a hard revocation for Riley, but does not goodlist // his commit. let riley_revocation_pgp = e.scratch_state().join("riley-revocation.pgp"); let mut f = std::fs::File::create(&riley_revocation_pgp).unwrap(); e.riley.hard_revoke().serialize(&mut f).unwrap(); drop(f); e.sq_git(&[ "policy", "authorize", "--cert-file", &riley_revocation_pgp.to_str().unwrap(), e.riley.petname, ])?; let commit_d = e.git_commit(&[("openpgp-policy.toml", None)], "D: willow imports hard revocation for riley", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_e = e.git_commit(&[("workwork.txt", Some(b"e"))], "E: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); e.sq_git(&[ "policy", "goodlist", &commit_b, ])?; let commit_f = e.git_commit(&[("openpgp-policy.toml", None)], "F: xander good lists riley's commit", Some(&e.xander)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); let commit_g = e.git_commit(&[("workwork.txt", Some(b"g"))], "G: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); Ok(()) } #[test] #[allow(unused_variables)] fn via() -> anyhow::Result<()> { let (e, root) = create_environment()?; // F <- target // / \ // E D <- unauthorized // | | // | C // \ / // B // | // A // // When we do: sq-git log --trust-root A E..F, this means we want // a valid path from A to F via E. Since E is unauthorized, this // should fail. let commit_a = e.git_commit(&[("workwork.txt", Some(b"1"))], "A: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_b = e.git_commit(&[("workwork.txt", Some(b"2"))], "B: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_c = e.git_commit(&[("workwork.txt", Some(b"3"))], "C: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); let commit_d = e.git_commit(&[("workwork.txt", Some(b"4"))], "D: willow signs a commit", Some(&e.willow_release)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Reset to b. e.git(&["checkout", &commit_b])?; e.git(&["clean", "-fdx"])?; // Unauthorized. let commit_e = e.git_commit(&[("busybusy.txt", Some(b"1"))], "E: xander signs a commit", Some(&e.xander)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_err()); // Merge D and E. e.git(&[ "merge", "-m", "F: Merge D and E", &format!("-S{}", e.willow_release.fingerprint), &commit_d, &commit_e])?; let commit_f = e.git_current_commit()?; assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..", root)]).is_ok()); // Make sure that we can go via D. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", commit_d, commit_f)]).is_ok()); // But not via E. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", commit_e, commit_f)]).is_err()); // We can't authenticate E via F, as F comes later. assert!(e.sq_git(&["log", "--trust-root", &root, &format!("{}..{}", commit_f, commit_e)]).is_err()); Ok(()) } #[test] fn external_policy_authenticates_trust_root() -> anyhow::Result<()> { // Normally the trust root is implicitly trusted. When we use an // external policy, we also use it to check the trust root. Make // sure that works. let (e, root) = create_environment()?; // Xander is not allowed to sign a commit. let commit_a = e.git_commit(&[("workwork.txt", Some(b"1"))], "A: xander signs a commit", Some(&e.xander)).unwrap(); // Willow is. let _commit_b = e.git_commit(&[("workwork.txt", Some(b"2"))], "B: willow signs a commit", Some(&e.willow)).unwrap(); // A is bad: Xander is not allowed to make a commit. assert!(e.sq_git(&["log", "--trust-root", &root]).is_err()); // When A is the trust root, it is implicitly trusted, and the // rest of the commits are good. assert!(e.sq_git(&["log", "--trust-root", &commit_a]).is_ok()); // But when we use an external policy, we also use the policy to // check A. So, it is bad again. assert!(e.sq_git(&["log", "--trust-root", &commit_a, "--policy-file", "openpgp-policy.toml"]).is_err()); // Authorize Xander. e.sq_git(&[ "policy", "authorize", e.xander.petname, &e.xander.fingerprint.to_string(), "--sign-commit", ])?; // When we use an external policy that says that Xander is // authorized, we're good again. assert!(e.sq_git(&["log", "--trust-root", &commit_a, "--policy-file", "openpgp-policy.toml"]).is_ok()); Ok(()) } #[test] fn symbolic_names() -> anyhow::Result<()> { // Make sure that symbolic names resolve. let (e, root) = create_environment()?; // Willow signs a commit. e.git(&["checkout", "-b", "commit-a"])?; let commit_a = e.git_commit(&[("workwork.txt", Some(b"1"))], "A: willow signs a commit", Some(&e.willow)).unwrap(); e.git(&["checkout", "-b", "commit-b"])?; let commit_b = e.git_commit(&[("workwork.txt", Some(b"2"))], "B: willow signs a commit", Some(&e.willow)).unwrap(); e.git(&["checkout", "-b", "commit-c"])?; let commit_c = e.git_commit(&[("workwork.txt", Some(b"3"))], "C: willow signs a commit", Some(&e.willow)).unwrap(); assert!(e.sq_git(&["log", "--trust-root", &root]).is_ok()); assert!(e.sq_git(&["log", "--trust-root", &commit_a]).is_ok()); assert!(e.sq_git(&["log", "--trust-root", "commit-a"]).is_ok()); // Name doesn't exist... assert!(e.sq_git(&["log", "--trust-root", "commit_a"]).is_err()); // Symbolic names in different places. assert!(e.sq_git(&["log", "--trust-root", "commit-a", &commit_b]).is_ok()); assert!(e.sq_git(&["log", "--trust-root", "commit-a", &format!("{}..{}", commit_b, commit_c)]).is_ok()); // Mix of hashes and symbolic names. assert!(e.sq_git(&["log", "--trust-root", "commit-a", &format!("commit-b..{}", commit_c)]).is_ok()); assert!(e.sq_git(&["log", "--trust-root", "commit-a", &format!("{}..commit-c", commit_b)]).is_ok()); assert!(e.sq_git(&["log", "--trust-root", "commit-a", "commit-b..commit-c"]).is_ok()); Ok(()) }