/ tests / e2e / test_key_rotation.py
test_key_rotation.py
  1  """E2E tests for key rotation and revocation flows."""
  2  
  3  from pathlib import Path
  4  
  5  import pytest
  6  
  7  from helpers.cli import get_device_did, run_auths
  8  from helpers.git import configure_signing, make_commit
  9  
 10  
 11  def _generate_allowed_signers(auths_bin, git_repo: Path, env: dict) -> Path:
 12      """Generate allowed-signers file inside the git repo's .auths/ dir."""
 13      auths_dir = git_repo / ".auths"
 14      auths_dir.mkdir(exist_ok=True)
 15      signers_file = auths_dir / "allowed_signers"
 16      run_auths(
 17          auths_bin,
 18          [
 19              "git",
 20              "allowed-signers",
 21              "--repo",
 22              env["AUTHS_HOME"],
 23              "--output",
 24              str(signers_file),
 25          ],
 26          env=env,
 27      ).assert_success()
 28      return signers_file
 29  
 30  
 31  def _link_device(auths_bin, env, *, capabilities=None):
 32      """Link a device and return the CLI result."""
 33      did = get_device_did(auths_bin, env)
 34      args = [
 35          "device",
 36          "link",
 37          "--identity-key-alias",
 38          "main",
 39          "--device-key-alias",
 40          "main",
 41          "--device-did",
 42          did,
 43      ]
 44      if capabilities:
 45          args += ["--capabilities", capabilities]
 46      return run_auths(auths_bin, args, env=env)
 47  
 48  
 49  @pytest.mark.slow
 50  @pytest.mark.requires_binary
 51  class TestKeyRotation:
 52      def test_rotate_keys(self, auths_bin, init_identity):
 53          id_before = run_auths(auths_bin, ["id", "show"], env=init_identity)
 54          id_before.assert_success()
 55  
 56          result = run_auths(auths_bin, ["id", "rotate"], env=init_identity)
 57          if result.returncode != 0:
 58              pytest.skip(f"id rotate not available: {result.stderr}")
 59          result.assert_success()
 60  
 61          id_after = run_auths(auths_bin, ["id", "show"], env=init_identity)
 62          id_after.assert_success()
 63  
 64      def test_verify_old_commit_after_rotation(
 65          self, auths_bin, auths_sign_bin, init_identity, git_repo
 66      ):
 67          configure_signing(git_repo, auths_sign_bin, init_identity)
 68          sha_a = make_commit(git_repo, "before rotation", init_identity)
 69  
 70          rotate = run_auths(auths_bin, ["id", "rotate"], env=init_identity)
 71          if rotate.returncode != 0:
 72              pytest.skip("id rotate not available")
 73  
 74          sha_b = make_commit(git_repo, "after rotation", init_identity)
 75  
 76          _generate_allowed_signers(auths_bin, git_repo, init_identity)
 77  
 78          verify_a = run_auths(
 79              auths_bin, ["verify", sha_a], cwd=git_repo, env=init_identity
 80          )
 81          if verify_a.returncode != 0:
 82              pytest.skip(f"verify not available: {verify_a.stderr}")
 83  
 84      def test_emergency_freeze(self, auths_bin, init_identity):
 85          result = run_auths(
 86              auths_bin, ["emergency", "freeze", "--yes"], env=init_identity
 87          )
 88          if result.returncode != 0:
 89              pytest.skip(f"emergency freeze not available: {result.stderr}")
 90          result.assert_success()
 91  
 92          status = run_auths(auths_bin, ["status"], env=init_identity)
 93          assert status.returncode in (0, 1)
 94  
 95      def test_emergency_unfreeze(self, auths_bin, init_identity):
 96          freeze = run_auths(
 97              auths_bin, ["emergency", "freeze", "--yes"], env=init_identity
 98          )
 99          if freeze.returncode != 0:
100              pytest.skip("emergency freeze not available")
101  
102          unfreeze = run_auths(
103              auths_bin, ["emergency", "unfreeze", "--yes"], env=init_identity
104          )
105          if unfreeze.returncode != 0:
106              pytest.skip(f"emergency unfreeze not available: {unfreeze.stderr}")
107          unfreeze.assert_success()
108  
109      def test_rotate_preserves_attestations(self, auths_bin, init_identity):
110          link = _link_device(
111              auths_bin, init_identity, capabilities="sign:commit"
112          )
113          if link.returncode != 0:
114              pytest.skip("device link not available")
115  
116          rotate = run_auths(auths_bin, ["id", "rotate"], env=init_identity)
117          if rotate.returncode != 0:
118              pytest.skip("id rotate not available")
119  
120          # After rotation, the device should still be listed
121          list_result = run_auths(
122              auths_bin, ["device", "list"], env=init_identity
123          )
124          list_result.assert_success()
125          did = get_device_did(auths_bin, init_identity)
126          assert "did:key:" in did