use std::fs;

use sequoia_openpgp as openpgp;
use openpgp::serialize::Serialize;

mod common;
use common::Environment;
use common::Result;

fn create_environment() -> 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(())
}
