Convert `tests.SSHTestKey` into a named tuple
Marco Ricci

Marco Ricci commited on 2025-01-16 00:55:16
Zeige 3 geänderte Dateien mit 105 Einfügungen und 106 Löschungen.


Again, this makes the notation more readable, and allows test keys to
contain methods.  Use this to group keys into always suitable keys and
other keys, instead of having to manually define appropriate tables.
... ...
@@ -34,15 +34,19 @@ if TYPE_CHECKING:
34 34
     from collections.abc import Callable, Iterator, Mapping, Sequence
35 35
 
36 36
     import click.testing
37
-    from typing_extensions import Any, NotRequired, TypedDict
37
+    from typing_extensions import Any
38 38
 
39
-    class SSHTestKey(TypedDict):
40
-        private_key: bytes
41
-        private_key_blob: NotRequired[bytes]
39
+
40
+class SSHTestKey(NamedTuple):
42 41
     public_key: bytes | str
43 42
     public_key_data: bytes
44
-        expected_signature: bytes | None
45
-        derived_passphrase: bytes | str | None
43
+    private_key: bytes
44
+    private_key_blob: bytes | None = None
45
+    expected_signature: bytes | None = None
46
+    derived_passphrase: bytes | str | None = None
47
+
48
+    def is_suitable(self) -> bool:
49
+        return vault.Vault.is_suitable_ssh_key(self.public_key_data)
46 50
 
47 51
 
48 52
 class ValidationSettings(NamedTuple):
... ...
@@ -463,9 +467,9 @@ class RunningSSHAgentInfo(NamedTuple):
463 467
     agent_type: KnownSSHAgent
464 468
 
465 469
 
466
-SUPPORTED_KEYS: Mapping[str, SSHTestKey] = {
467
-    'ed25519': {
468
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
470
+ALL_KEYS: Mapping[str, SSHTestKey] = {
471
+    'ed25519': SSHTestKey(
472
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
469 473
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
470 474
 QyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdgAAAKDweO7H8Hju
471 475
 xwAAAAtzc2gtZWQyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdg
... ...
@@ -473,7 +477,7 @@ AAAEAbM/A869nkWZbe2tp3Dm/L6gitvmpH/aRZt8sBII3ExYF4gWgm1gJIXw//Mkhv5MEw
473 477
 idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC
474 478
 -----END OPENSSH PRIVATE KEY-----
475 479
 """,
476
-        'private_key_blob': bytes.fromhex("""
480
+        private_key_blob=bytes.fromhex("""
477 481
             00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
478 482
             00 00 00 20
479 483
             81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1
... ...
@@ -486,15 +490,15 @@ idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC
486 490
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 74
487 491
             68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
488 492
 """),
489
-        'public_key': rb"""ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIF4gWgm1gJIXw//Mkhv5MEwidwcakUGCekJD/vCEml2 test key without passphrase
493
+        public_key=rb"""ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIF4gWgm1gJIXw//Mkhv5MEwidwcakUGCekJD/vCEml2 test key without passphrase
490 494
 """,
491
-        'public_key_data': bytes.fromhex("""
495
+        public_key_data=bytes.fromhex("""
492 496
             00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
493 497
             00 00 00 20
494 498
             81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1
495 499
             30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76
496 500
 """),
497
-        'expected_signature': bytes.fromhex("""
501
+        expected_signature=bytes.fromhex("""
498 502
             00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
499 503
             00 00 00 40
500 504
             f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86
... ...
@@ -502,12 +506,12 @@ idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC
502 506
             0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
503 507
             1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
504 508
         """),
505
-        'derived_passphrase': rb'8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==',
506
-    },
509
+        derived_passphrase=rb'8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==',
510
+    ),
507 511
     # Currently only supported by PuTTY (which is deficient in other
508 512
     # niceties of the SSH agent and the agent's client).
509
-    'ed448': {
510
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
513
+    'ed448': SSHTestKey(
514
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
511 515
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
512 516
 c2gtZWQ0NDgAAAA54vZy009Wu8wExjvEb3hqtLz1GO/+d5vmGUbErWQ4AUO9mYLT
513 517
 zHJHc2m4s+yWzP29Cc3EcxizLG8AAAAA8BdhfCcXYXwnAAAACXNzaC1lZDQ0OAAA
... ...
@@ -518,7 +522,7 @@ GUbErWQ4AUO9mYLTzHJHc2m4s+yWzP29Cc3EcxizLG8AAAAAG3Rlc3Qga2V5IHdp
518 522
 dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ==
519 523
 -----END OPENSSH PRIVATE KEY-----
520 524
 """,
521
-        'private_key_blob': bytes.fromhex("""
525
+        private_key_blob=bytes.fromhex("""
522 526
             00 00 00 09 73 73 68 2d 65 64 34 34 38
523 527
             00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04
524 528
             c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19
... ...
@@ -535,16 +539,16 @@ dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ==
535 539
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
536 540
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
537 541
 """),
538
-        'public_key': rb"""ssh-ed448 AAAACXNzaC1lZDQ0OAAAADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM/b0JzcRzGLMsbwA= test key without passphrase
542
+        public_key=rb"""ssh-ed448 AAAACXNzaC1lZDQ0OAAAADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM/b0JzcRzGLMsbwA= test key without passphrase
539 543
 """,
540
-        'public_key_data': bytes.fromhex("""
544
+        public_key_data=bytes.fromhex("""
541 545
             00 00 00 09 73 73 68 2d 65 64 34 34 38
542 546
             00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04
543 547
             c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19
544 548
             46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69
545 549
             b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00
546 550
         """),
547
-        'expected_signature': bytes.fromhex("""
551
+        expected_signature=bytes.fromhex("""
548 552
             00 00 00 09 73 73 68 2d 65 64 34 34 38
549 553
             00 00 00 72 06 86
550 554
             f4 64 a4 a6 ba d9 c3 22 c4 93 49 99 fc 11 de 67
... ...
@@ -555,10 +559,10 @@ dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ==
555 559
             db bd 77 7c 80 20 7f 3a 48 61 f6 1f ae a9 5e 53
556 560
             7b e0 9d 93 1e ea dc eb b5 cd 56 4c ea 8f 08 00
557 561
         """),
558
-        'derived_passphrase': rb'Bob0ZKSmutnDIsSTSZn8Ed5nlwjy2Lc8LBPnxRwekqYO2C9tgQOCAONy5DJtctJtMoQ/zKkeVywAmrOZ3kXazi7R2+WJ8zW+JFiQxsoE8NuIgNu9d3yAIH86SGH2H66pXlN74J2THurc67XNVkzqjwgA',
559
-    },
560
-    'rsa': {
561
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
562
+        derived_passphrase=rb'Bob0ZKSmutnDIsSTSZn8Ed5nlwjy2Lc8LBPnxRwekqYO2C9tgQOCAONy5DJtctJtMoQ/zKkeVywAmrOZ3kXazi7R2+WJ8zW+JFiQxsoE8NuIgNu9d3yAIH86SGH2H66pXlN74J2THurc67XNVkzqjwgA',
563
+    ),
564
+    'rsa': SSHTestKey(
565
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
562 566
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
563 567
 NhAAAAAwEAAQAAAYEAsaHu6Xs4cVsuDSNJlMCqoPVgmDgEviI8TfXmHKqX3JkIqI3LsvV7
564 568
 Ijf8WCdTveEq7CkuZhImtsR52AOEVAoU8mDXDNr+nJ5wUPzf1UIaRjDe0lcXW4SlF01hQs
... ...
@@ -597,7 +601,7 @@ btBNdMEJJp7ifucYmoYAAwE7K+VlWagDEK2y8Mte9y9E+N0uO2j+h85sQt/UIb2iE/vhcg
597 601
 Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB
598 602
 -----END OPENSSH PRIVATE KEY-----
599 603
 """,
600
-        'private_key_blob': bytes.fromhex("""
604
+        private_key_blob=bytes.fromhex("""
601 605
             00 00 00 07 73 73 68 2d 72 73 61
602 606
             00 00 01 81 00
603 607
             b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa a0
... ...
@@ -692,9 +696,9 @@ Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB
692 696
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
693 697
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
694 698
 """),
695
-        'public_key': rb"""ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxoe7pezhxWy4NI0mUwKqg9WCYOAS+IjxN9eYcqpfcmQiojcuy9XsiN/xYJ1O94SrsKS5mEia2xHnYA4RUChTyYNcM2v6cnnBQ/N/VQhpGMN7SVxdbhKUXTWFCwbjBgO6rGyHB6WtoH8vd7TOEPt+NgcXwhsWyoaUUdYTA62V+GF9vEmxMaC4ubgDz+B0QkPnauSoNxmkhcIe0lsLNb1pClZyz88PDnKXCX/d0HuN/HJ+sbPg7dCvOyqFYSyKn3uY6bCXqoIdurxXzH3O7z0P8f5sbmKOrGGKNuNxVRbeVl/D/3uDL0nqsbfUc1qvkfwbJwtMXC4IV6kOZMSk2BAsqh7x48gQ+rhYeEVSi8F3CWs4HJQoqrGt7K9a3mCSlMBHP70u3w6ME7eumoryxlUofewTd17ZEkzdX08l2ZlKzZvwQUrc+xQZ2Uw8z2mfW6Ti4gi0pYGaig7Ke4PwuXpo/C5YAWfeXycsvJZ2uaYRjMdZeJGNAnHLUGLkBscw5aI8= test key without passphrase
699
+        public_key=rb"""ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxoe7pezhxWy4NI0mUwKqg9WCYOAS+IjxN9eYcqpfcmQiojcuy9XsiN/xYJ1O94SrsKS5mEia2xHnYA4RUChTyYNcM2v6cnnBQ/N/VQhpGMN7SVxdbhKUXTWFCwbjBgO6rGyHB6WtoH8vd7TOEPt+NgcXwhsWyoaUUdYTA62V+GF9vEmxMaC4ubgDz+B0QkPnauSoNxmkhcIe0lsLNb1pClZyz88PDnKXCX/d0HuN/HJ+sbPg7dCvOyqFYSyKn3uY6bCXqoIdurxXzH3O7z0P8f5sbmKOrGGKNuNxVRbeVl/D/3uDL0nqsbfUc1qvkfwbJwtMXC4IV6kOZMSk2BAsqh7x48gQ+rhYeEVSi8F3CWs4HJQoqrGt7K9a3mCSlMBHP70u3w6ME7eumoryxlUofewTd17ZEkzdX08l2ZlKzZvwQUrc+xQZ2Uw8z2mfW6Ti4gi0pYGaig7Ke4PwuXpo/C5YAWfeXycsvJZ2uaYRjMdZeJGNAnHLUGLkBscw5aI8= test key without passphrase
696 700
 """,
697
-        'public_key_data': bytes.fromhex("""
701
+        public_key_data=bytes.fromhex("""
698 702
             00 00 00 07 73 73 68 2d 72 73 61
699 703
             00 00 00 03 01 00 01
700 704
             00 00 01 81 00
... ...
@@ -723,7 +727,7 @@ Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB
723 727
             0b 96 00 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31
724 728
             d6 5e 24 63 40 9c 72 d4 18 b9 01 b1 cc 39 68 8f
725 729
 """),
726
-        'expected_signature': bytes.fromhex("""
730
+        expected_signature=bytes.fromhex("""
727 731
             00 00 00 07 73 73 68 2d 72 73 61
728 732
             00 00 01 80
729 733
             a2 10 7c 2e f6 bb 53 a8 74 2a a1 19 99 ad 81 be
... ...
@@ -751,13 +755,10 @@ Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB
751 755
             de 69 2c 48 62 d9 fd d1 9b 6b b0 49 db d3 ff 38
752 756
             e7 10 d9 2d ce 9f 0d 5e 09 7b 37 d2 7b c3 bf ce
753 757
 """),
754
-        'derived_passphrase': rb'ohB8Lva7U6h0KqEZma2Bvnmc7dadCU5uxRhIM5B3mWj3ngNazU4Y64l9haLurkqS9m/Ouf6GfyprMdpuGv6ipYi4RH+hdnOz7HW10Ka5FZdlCRN9lCHR+10PiyMEd8LDVSKxoAmK9Tgq1n8bhymgJdMlb8tkYQeY3BTFhPiSJF5QEWtJ5fDMKcspqRnYp3EfkQsFsQFLwl8ApbYhv/gsnWebRzsKSWt5Lfwd7Ayw5Sci1an408P530ho6fvvPNwmv8/qKUMBpuPFUZX0Zm2KVeJH7OgwRUyuR+fJpCGLZLq2iPYh+HO5yxGheHWSxlrlZP7tQtmVmeYrbzwWPCh0pHIvDT8sM2eqNRmO57URL7P3asUC4m+jQuNiGZkD6qUg56HjvMgGo7V81nZd329gRoMqCADW09mkwUGM+GBWRYHaO6IWH55OdYMX2sNTwz4ZpBu80im4eGEreOaxUrDV7N5pLEhi2f3Rm2uwSdvT/zjnENktzp8NXgl7N9J7w7/O',
755
-    },
756
-}
757
-
758
-UNSUITABLE_KEYS: Mapping[str, SSHTestKey] = {
759
-    'dsa1024': {
760
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
758
+        derived_passphrase=rb'ohB8Lva7U6h0KqEZma2Bvnmc7dadCU5uxRhIM5B3mWj3ngNazU4Y64l9haLurkqS9m/Ouf6GfyprMdpuGv6ipYi4RH+hdnOz7HW10Ka5FZdlCRN9lCHR+10PiyMEd8LDVSKxoAmK9Tgq1n8bhymgJdMlb8tkYQeY3BTFhPiSJF5QEWtJ5fDMKcspqRnYp3EfkQsFsQFLwl8ApbYhv/gsnWebRzsKSWt5Lfwd7Ayw5Sci1an408P530ho6fvvPNwmv8/qKUMBpuPFUZX0Zm2KVeJH7OgwRUyuR+fJpCGLZLq2iPYh+HO5yxGheHWSxlrlZP7tQtmVmeYrbzwWPCh0pHIvDT8sM2eqNRmO57URL7P3asUC4m+jQuNiGZkD6qUg56HjvMgGo7V81nZd329gRoMqCADW09mkwUGM+GBWRYHaO6IWH55OdYMX2sNTwz4ZpBu80im4eGEreOaxUrDV7N5pLEhi2f3Rm2uwSdvT/zjnENktzp8NXgl7N9J7w7/O',
759
+    ),
760
+    'dsa1024': SSHTestKey(
761
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
761 762
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
762 763
 NzAAAAgQC7KAZXqBGNVLBQPrcMYAoNW54BhD8aIhe7BDWYzJcsaMt72VKSkguZ8+XR7nRa
763 764
 0C/ZsBi+uJp0dpxy9ZMTOWX4u5YPMeQcXEdGExZIfimGqSOAsy6fCld2IfJZJZExcCmhe9
... ...
@@ -779,7 +780,7 @@ u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW
779 780
 0gAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UBAgMEBQYH
780 781
 -----END OPENSSH PRIVATE KEY-----
781 782
 """,
782
-        'private_key_blob': bytes.fromhex("""
783
+        private_key_blob=bytes.fromhex("""
783 784
             00 00 00 07 73 73 68 2d 64 73 73
784 785
             00 00 00 81 00
785 786
             bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d
... ...
@@ -815,9 +816,9 @@ u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW
815 816
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
816 817
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
817 818
 """),
818
-        'public_key': rb"""ssh-dss AAAAB3NzaC1kc3MAAACBALsoBleoEY1UsFA+twxgCg1bngGEPxoiF7sENZjMlyxoy3vZUpKSC5nz5dHudFrQL9mwGL64mnR2nHL1kxM5Zfi7lg8x5BxcR0YTFkh+KYapI4CzLp8KV3Yh8lklkTFwKaF71KyOx3dhIA8lGW45cVBz3kxmhHmEzCUgMPxDOsTtAAAAFQD32c5k6B3tocxUahelQQFyfseiywAAAIAuvYCDeHEzesp3HNVTDx9fRVU9c77f4qvyEZ7Qpz/s3BVoFUvUZDx96cG5bKekBRsfTCjeHXCQH/yFfqn5Lxye7msgGVS5U3AvD9shiiEr3wt+pNgr9X6DooP7ybfjC8SJdmarLBjnifZuSxyHU2q+P+02kvMTFLH9dLSRIzVqKAAAAIBtA1E9xUS4YOsRx/7GDm2AB6M9cE9ev8myz4KGTriSbeaKsxiMBbJZi1VyBP7uE5jG1hGKfwvIwuopGaprRDlSu8N8KGAuG+wb1hJv8ynDmqbw+IdJp/CGRrP+17f7yEqiCqh7ux360IXToikmvmTvQAKI21Eaqyw0XwSWuXV59g== test key without passphrase
819
+        public_key=rb"""ssh-dss AAAAB3NzaC1kc3MAAACBALsoBleoEY1UsFA+twxgCg1bngGEPxoiF7sENZjMlyxoy3vZUpKSC5nz5dHudFrQL9mwGL64mnR2nHL1kxM5Zfi7lg8x5BxcR0YTFkh+KYapI4CzLp8KV3Yh8lklkTFwKaF71KyOx3dhIA8lGW45cVBz3kxmhHmEzCUgMPxDOsTtAAAAFQD32c5k6B3tocxUahelQQFyfseiywAAAIAuvYCDeHEzesp3HNVTDx9fRVU9c77f4qvyEZ7Qpz/s3BVoFUvUZDx96cG5bKekBRsfTCjeHXCQH/yFfqn5Lxye7msgGVS5U3AvD9shiiEr3wt+pNgr9X6DooP7ybfjC8SJdmarLBjnifZuSxyHU2q+P+02kvMTFLH9dLSRIzVqKAAAAIBtA1E9xUS4YOsRx/7GDm2AB6M9cE9ev8myz4KGTriSbeaKsxiMBbJZi1VyBP7uE5jG1hGKfwvIwuopGaprRDlSu8N8KGAuG+wb1hJv8ynDmqbw+IdJp/CGRrP+17f7yEqiCqh7ux360IXToikmvmTvQAKI21Eaqyw0XwSWuXV59g== test key without passphrase
819 820
 """,
820
-        'public_key_data': bytes.fromhex("""
821
+        public_key_data=bytes.fromhex("""
821 822
             00 00 00 07 73 73 68 2d 64 73 73
822 823
             00 00 00 81 00
823 824
             bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d
... ...
@@ -849,11 +850,11 @@ u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW
849 850
             a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef
850 851
             40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6
851 852
 """),
852
-        'expected_signature': None,
853
-        'derived_passphrase': None,
854
-    },
855
-    'ecdsa256': {
856
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
853
+        expected_signature=None,
854
+        derived_passphrase=None,
855
+    ),
856
+    'ecdsa256': SSHTestKey(
857
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
857 858
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
858 859
 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTLbU0zDwsk2Dvp+VYIrsNVf5gWwz2S
859 860
 3SZ8TbxiQRkpnGSVqyIoHJOJc+NQItAa7xlJ/8Z6gfz57Z3apUkaMJm6AAAAuKeY+YinmP
... ...
@@ -863,7 +864,7 @@ oAAAAhAKIl/3n0pKVIxpZkXTGtii782Qr4yIcvHdpxjO/QsIqKAAAAG3Rlc3Qga2V5IHdp
863 864
 dGhvdXQgcGFzc3BocmFzZQECAwQ=
864 865
 -----END OPENSSH PRIVATE KEY-----
865 866
 """,
866
-        'private_key_blob': bytes.fromhex("""
867
+        private_key_blob=bytes.fromhex("""
867 868
             00 00 00 13 65 63 64
868 869
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36
869 870
             00 00 00 08 6e 69 73 74 70 32 35 36
... ...
@@ -878,9 +879,9 @@ dGhvdXQgcGFzc3BocmFzZQECAwQ=
878 879
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
879 880
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
880 881
 """),
881
-        'public_key': rb"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmbo= test key without passphrase
882
+        public_key=rb"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmbo= test key without passphrase
882 883
 """,
883
-        'public_key_data': bytes.fromhex("""
884
+        public_key_data=bytes.fromhex("""
884 885
             00 00 00 13 65 63 64
885 886
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36
886 887
             00 00 00 08 6e 69 73 74 70 32 35 36
... ...
@@ -890,11 +891,11 @@ dGhvdXQgcGFzc3BocmFzZQECAwQ=
890 891
             64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19
891 892
             49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba
892 893
 """),
893
-        'expected_signature': None,
894
-        'derived_passphrase': None,
895
-    },
896
-    'ecdsa384': {
897
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
894
+        expected_signature=None,
895
+        derived_passphrase=None,
896
+    ),
897
+    'ecdsa384': SSHTestKey(
898
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
898 899
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
899 900
 1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSgkOjkAvq7v5vHuj3KBL4/EAWcn5hZ
900 901
 DyKcbyV0eBMGFq7hKXQlZqIahLVqeMR0QqmkxNJ2rly2VHcXneq3vZ+9fIsWCOdYk5WP3N
... ...
@@ -905,7 +906,7 @@ JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B
905 906
 2OdqVOVRkwZWRROz0AAAAbdGVzdCBrZXkgd2l0aG91dCBwYXNzcGhyYXNlAQIDBA==
906 907
 -----END OPENSSH PRIVATE KEY-----
907 908
 """,
908
-        'private_key_blob': bytes.fromhex("""
909
+        private_key_blob=bytes.fromhex("""
909 910
             00 00 00 13 65 63 64
910 911
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34
911 912
             00 00 00 08 6e 69 73 74 70 33 38 34
... ...
@@ -923,9 +924,9 @@ JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B
923 924
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
924 925
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
925 926
 """),
926
-        'public_key': rb"""ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKCQ6OQC+ru/m8e6PcoEvj8QBZyfmFkPIpxvJXR4EwYWruEpdCVmohqEtWp4xHRCqaTE0nauXLZUdxed6re9n718ixYI51iTlY/c1k/O/3XVefvBsSQLtCd0PnMqWbikFQ== test key without passphrase
927
+        public_key=rb"""ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKCQ6OQC+ru/m8e6PcoEvj8QBZyfmFkPIpxvJXR4EwYWruEpdCVmohqEtWp4xHRCqaTE0nauXLZUdxed6re9n718ixYI51iTlY/c1k/O/3XVefvBsSQLtCd0PnMqWbikFQ== test key without passphrase
927 928
 """,
928
-        'public_key_data': bytes.fromhex("""
929
+        public_key_data=bytes.fromhex("""
929 930
             00 00 00 13 65 63 64
930 931
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34
931 932
             00 00 00 08 6e 69 73 74 70 33 38 34
... ...
@@ -937,11 +938,11 @@ JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B
937 938
             7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5
938 939
             79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15
939 940
 """),
940
-        'expected_signature': None,
941
-        'derived_passphrase': None,
942
-    },
943
-    'ecdsa521': {
944
-        'private_key': rb"""-----BEGIN OPENSSH PRIVATE KEY-----
941
+        expected_signature=None,
942
+        derived_passphrase=None,
943
+    ),
944
+    'ecdsa521': SSHTestKey(
945
+        private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY-----
945 946
 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
946 947
 1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQASVOdwDznmlcGqiLvFtYeVtrAEiVz
947 948
 iIfsL7jEM8Utu/m8WSkPFQtjwqdFw+WfZ0mi6qMbEFgi/ELzZSKVteCSbcMAhqAkOMFKiD
... ...
@@ -954,7 +955,7 @@ U3otwLYnuj+/5AdzuB/zotu95UdFv9I2DNXzd9E4WAyz6IqBBNcsMkxrzHAdqsYDAAAAG3
954 955
 Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQ==
955 956
 -----END OPENSSH PRIVATE KEY-----
956 957
 """,
957
-        'private_key_blob': bytes.fromhex("""
958
+        private_key_blob=bytes.fromhex("""
958 959
             00 00 00 13 65 63 64
959 960
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31
960 961
             00 00 00 08 6e 69 73 74 70 35 32 31
... ...
@@ -975,9 +976,9 @@ Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQ==
975 976
             00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69
976 977
             74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65
977 978
 """),
978
-        'public_key': rb"""ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABJU53APOeaVwaqIu8W1h5W2sASJXOIh+wvuMQzxS27+bxZKQ8VC2PCp0XD5Z9nSaLqoxsQWCL8QvNlIpW14JJtwwCGoCQ4wUqIO7hvG+wzptPTZG7urbJPjXJLIaFQPRDJIGcjoKS3/CdDVMSmPzMMqJESvGz17pAsYSU1GTMs8yz6Yw== test key without passphrase
979
+        public_key=rb"""ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABJU53APOeaVwaqIu8W1h5W2sASJXOIh+wvuMQzxS27+bxZKQ8VC2PCp0XD5Z9nSaLqoxsQWCL8QvNlIpW14JJtwwCGoCQ4wUqIO7hvG+wzptPTZG7urbJPjXJLIaFQPRDJIGcjoKS3/CdDVMSmPzMMqJESvGz17pAsYSU1GTMs8yz6Yw== test key without passphrase
979 980
 """,
980
-        'public_key_data': bytes.fromhex("""
981
+        public_key_data=bytes.fromhex("""
981 982
             00 00 00 13 65 63 64
982 983
             73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36
983 984
             00 00 00 08 6e 69 73 74 70 35 32 31
... ...
@@ -991,18 +992,24 @@ Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQ==
991 992
             23 a0 a4 b7 fc 27 43 54 c4 a6 3f 33 0c a8 91 12
992 993
             bc 6c f5 ee 90 2c 61 25 35 19 33 2c f3 2c fa 63
993 994
 """),
994
-        'expected_signature': None,
995
-        'derived_passphrase': None,
996
-    },
995
+        expected_signature=None,
996
+        derived_passphrase=None,
997
+    ),
998
+}
999
+SUPPORTED_KEYS: Mapping[str, SSHTestKey] = {
1000
+    k: v for k, v in ALL_KEYS.items() if v.is_suitable()
1001
+}
1002
+UNSUITABLE_KEYS: Mapping[str, SSHTestKey] = {
1003
+    k: v for k, v in ALL_KEYS.items() if not v.is_suitable()
997 1004
 }
998 1005
 
999 1006
 DUMMY_SERVICE = 'service1'
1000 1007
 DUMMY_PASSPHRASE = 'my secret passphrase'
1001
-DUMMY_KEY1 = SUPPORTED_KEYS['ed25519']['public_key_data']
1008
+DUMMY_KEY1 = SUPPORTED_KEYS['ed25519'].public_key_data
1002 1009
 DUMMY_KEY1_B64 = base64.standard_b64encode(DUMMY_KEY1).decode('ASCII')
1003
-DUMMY_KEY2 = SUPPORTED_KEYS['rsa']['public_key_data']
1010
+DUMMY_KEY2 = SUPPORTED_KEYS['rsa'].public_key_data
1004 1011
 DUMMY_KEY2_B64 = base64.standard_b64encode(DUMMY_KEY2).decode('ASCII')
1005
-DUMMY_KEY3 = SUPPORTED_KEYS['ed448']['public_key_data']
1012
+DUMMY_KEY3 = SUPPORTED_KEYS['ed448'].public_key_data
1006 1013
 DUMMY_KEY3_B64 = base64.standard_b64encode(DUMMY_KEY3).decode('ASCII')
1007 1014
 DUMMY_CONFIG_SETTINGS = {
1008 1015
     'length': 10,
... ...
@@ -1367,15 +1374,10 @@ def hypothesis_settings_coverage_compatible_with_caplog(
1367 1374
 def list_keys(self: Any = None) -> list[_types.SSHKeyCommentPair]:
1368 1375
     del self  # Unused.
1369 1376
     Pair = _types.SSHKeyCommentPair  # noqa: N806
1370
-    list1 = [
1371
-        Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
1372
-        for key, value in SUPPORTED_KEYS.items()
1373
-    ]
1374
-    list2 = [
1375
-        Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
1376
-        for key, value in UNSUITABLE_KEYS.items()
1377
+    return [
1378
+        Pair(value.public_key_data, f'{key} test key'.encode('ASCII'))
1379
+        for key, value in ALL_KEYS.items()
1377 1380
     ]
1378
-    return list1 + list2
1379 1381
 
1380 1382
 
1381 1383
 def sign(
... ...
@@ -1384,9 +1386,9 @@ def sign(
1384 1386
     del self  # Unused.
1385 1387
     assert message == vault.Vault._UUID
1386 1388
     for value in SUPPORTED_KEYS.values():
1387
-        if value['public_key_data'] == key:  # pragma: no branch
1388
-            assert value['expected_signature'] is not None
1389
-            return value['expected_signature']
1389
+        if value.public_key_data == key:  # pragma: no branch
1390
+            assert value.expected_signature is not None
1391
+            return value.expected_signature
1390 1392
     raise AssertionError
1391 1393
 
1392 1394
 
... ...
@@ -1394,7 +1396,7 @@ def list_keys_singleton(self: Any = None) -> list[_types.SSHKeyCommentPair]:
1394 1396
     del self  # Unused.
1395 1397
     Pair = _types.SSHKeyCommentPair  # noqa: N806
1396 1398
     list1 = [
1397
-        Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
1399
+        Pair(value.public_key_data, f'{key} test key'.encode('ASCII'))
1398 1400
         for key, value in SUPPORTED_KEYS.items()
1399 1401
     ]
1400 1402
     return list1[:1]
... ...
@@ -461,7 +461,6 @@ def ssh_agent_client_with_test_keys_loaded(  # noqa: C901
461 461
 
462 462
     """
463 463
     agent_type, client, isolated = spawn_ssh_agent
464
-    all_test_keys = {**tests.SUPPORTED_KEYS, **tests.UNSUITABLE_KEYS}
465 464
     successfully_loaded_keys: set[str] = set()
466 465
 
467 466
     def prepare_payload(
... ...
@@ -483,10 +482,9 @@ def ssh_agent_client_with_test_keys_loaded(  # noqa: C901
483 482
         return (return_code, bytes(payload) + lifetime_constraint)
484 483
 
485 484
     try:
486
-        for key_type, key_struct in all_test_keys.items():
487
-            try:
488
-                private_key_data = key_struct['private_key_blob']
489
-            except KeyError:  # pragma: no cover
485
+        for key_type, key_struct in tests.ALL_KEYS.items():
486
+            private_key_data = key_struct.private_key_blob
487
+            if private_key_data is None:  # pragma: no cover
490 488
                 continue
491 489
             request_code, payload = prepare_payload(
492 490
                 private_key_data, isolated=isolated, time_to_live=30
... ...
@@ -513,7 +511,7 @@ def ssh_agent_client_with_test_keys_loaded(  # noqa: C901
513 511
                         pair.key for pair in client.list_keys()
514 512
                     })
515 513
                     if agent_type == tests.KnownSSHAgent.Pageant and (
516
-                        key_struct['public_key_data'] in current_loaded_keys
514
+                        key_struct.public_key_data in current_loaded_keys
517 515
                     ):
518 516
                         pass
519 517
                     elif agent_type == tests.KnownSSHAgent.Pageant and (
... ...
@@ -539,14 +537,14 @@ def ssh_agent_client_with_test_keys_loaded(  # noqa: C901
539 537
                 successfully_loaded_keys.add(key_type)
540 538
         yield client
541 539
     finally:
542
-        for key_type, key_struct in all_test_keys.items():
540
+        for key_type, key_struct in tests.ALL_KEYS.items():
543 541
             if not isolated and (
544 542
                 key_type in successfully_loaded_keys
545 543
             ):  # pragma: no cover
546 544
                 # The public key blob is the base64-encoded part in
547 545
                 # the "public key line".
548 546
                 public_key = base64.standard_b64decode(
549
-                    key_struct['public_key'].split(None, 2)[1]
547
+                    key_struct.public_key.split(None, 2)[1]
550 548
                 )
551 549
                 request_code = _types.SSH_AGENTC.REMOVE_IDENTITY
552 550
                 client.request(
... ...
@@ -31,7 +31,7 @@ class TestStaticFunctionality:
31 31
     @pytest.mark.parametrize(
32 32
         ['public_key', 'public_key_data'],
33 33
         [
34
-            (val['public_key'], val['public_key_data'])
34
+            (val.public_key, val.public_key_data)
35 35
             for val in tests.SUPPORTED_KEYS.values()
36 36
         ],
37 37
     )
... ...
@@ -232,20 +232,22 @@ class TestStaticFunctionality:
232 232
 
233 233
 class TestAgentInteraction:
234 234
     @pytest.mark.parametrize(
235
-        'data_dict',
235
+        'ssh_test_key',
236 236
         list(tests.SUPPORTED_KEYS.values()),
237 237
         ids=tests.SUPPORTED_KEYS.keys(),
238 238
     )
239 239
     def test_200_sign_data_via_agent(
240 240
         self,
241 241
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
242
-        data_dict: tests.SSHTestKey,
242
+        ssh_test_key: tests.SSHTestKey,
243 243
     ) -> None:
244 244
         client = ssh_agent_client_with_test_keys_loaded
245 245
         key_comment_pairs = {bytes(k): bytes(c) for k, c in client.list_keys()}
246
-        public_key_data = data_dict['public_key_data']
247
-        expected_signature = data_dict['expected_signature']
248
-        derived_passphrase = data_dict['derived_passphrase']
246
+        public_key_data = ssh_test_key.public_key_data
247
+        expected_signature = ssh_test_key.expected_signature
248
+        derived_passphrase = ssh_test_key.derived_passphrase
249
+        assert expected_signature is not None
250
+        assert derived_passphrase is not None
249 251
         if public_key_data not in key_comment_pairs:  # pragma: no cover
250 252
             pytest.skip('prerequisite SSH key not loaded')
251 253
         signature = bytes(
... ...
@@ -262,19 +264,18 @@ class TestAgentInteraction:
262 264
         ), 'SSH signature mismatch'
263 265
 
264 266
     @pytest.mark.parametrize(
265
-        'data_dict',
267
+        'ssh_test_key',
266 268
         list(tests.UNSUITABLE_KEYS.values()),
267 269
         ids=tests.UNSUITABLE_KEYS.keys(),
268 270
     )
269 271
     def test_201_sign_data_via_agent_unsupported(
270 272
         self,
271 273
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
272
-        data_dict: tests.SSHTestKey,
274
+        ssh_test_key: tests.SSHTestKey,
273 275
     ) -> None:
274 276
         client = ssh_agent_client_with_test_keys_loaded
275 277
         key_comment_pairs = {bytes(k): bytes(c) for k, c in client.list_keys()}
276
-        public_key_data = data_dict['public_key_data']
277
-        _ = data_dict['expected_signature']
278
+        public_key_data = ssh_test_key.public_key_data
278 279
         if public_key_data not in key_comment_pairs:  # pragma: no cover
279 280
             pytest.skip('prerequisite SSH key not loaded')
280 281
         assert not vault.Vault.is_suitable_ssh_key(
... ...
@@ -288,7 +289,7 @@ class TestAgentInteraction:
288 289
     @pytest.mark.parametrize(
289 290
         ['key', 'single'],
290 291
         [
291
-            (value['public_key_data'], False)
292
+            (value.public_key_data, False)
292 293
             for value in tests.SUPPORTED_KEYS.values()
293 294
         ]
294 295
         + [(tests.list_keys_singleton()[0].key, True)],
... ...
@@ -303,11 +304,9 @@ class TestAgentInteraction:
303 304
         client = ssh_agent_client_with_test_keys_loaded
304 305
 
305 306
         def key_is_suitable(key: bytes) -> bool:
306
-            always = {
307
-                v['public_key_data'] for v in tests.SUPPORTED_KEYS.values()
308
-            }
307
+            always = {v.public_key_data for v in tests.SUPPORTED_KEYS.values()}
309 308
             dsa = {
310
-                v['public_key_data']
309
+                v.public_key_data
311 310
                 for k, v in tests.UNSUITABLE_KEYS.items()
312 311
                 if k.startswith(('dsa', 'ecdsa'))
313 312
             }
... ...
@@ -503,7 +502,7 @@ class TestAgentInteraction:
503 502
                 'target SSH key not loaded into agent',
504 503
             ),
505 504
             (
506
-                tests.SUPPORTED_KEYS['ed25519']['public_key_data'],
505
+                tests.SUPPORTED_KEYS['ed25519'].public_key_data,
507 506
                 True,
508 507
                 _types.SSH_AGENT.FAILURE,
509 508
                 b'',
... ...
@@ -562,7 +561,7 @@ class TestAgentInteraction:
562 561
             monkeypatch2.setattr(client, 'request', request)
563 562
             SSHKeyCommentPair = _types.SSHKeyCommentPair  # noqa: N806
564 563
             loaded_keys = [
565
-                SSHKeyCommentPair(v['public_key_data'], b'no comment')
564
+                SSHKeyCommentPair(v.public_key_data, b'no comment')
566 565
                 for v in tests.SUPPORTED_KEYS.values()
567 566
             ]
568 567
             monkeypatch2.setattr(client, 'list_keys', lambda: loaded_keys)
569 568