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