Marco Ricci commited on 2025-08-08 22:58:18
Zeige 15 geänderte Dateien mit 3928 Einfügungen und 3620 Löschungen.
Split the top-level `tests` module into a pair of subpackages `tests.data` and `tests.machinery`, grouping them by import requirements. This "stratifies" the data, and (in some cases) reduces the amount of necessary support code while also (always) forcing us to carefully consider the scope and the prerequisites of every test support datum or function. As a practical example of the benefits of this stratification, the `test_key_signatures_and_outputs.py` script can get away with importing only the lowest stratum `tests.data`, and thus does not need `pytest` or `hypothesis` installed to run. There are five strata, defined by the five subordinate modules in the `tests` package: `tests.data`, `tests.data.callables`, `tests.machinery`, `tests.machinery.pytest` and `tests.machinery.hypothesis`. Each module may import from modules earlier in the list, but not later. The `tests.data.callables` submodule and the `tests.machinery` subpackage differ in that the `test.data.callables` are still expected to be functional and useful even without a testing system, the latter not necessarily.
... | ... |
@@ -28,7 +28,7 @@ from __future__ import annotations |
28 | 28 |
import argparse |
29 | 29 |
import textwrap |
30 | 30 |
|
31 |
-import tests |
|
31 |
+import tests.data |
|
32 | 32 |
|
33 | 33 |
from derivepassphrase import _types, ssh_agent, vault # noqa: PLC2701 |
34 | 34 |
|
... | ... |
@@ -39,9 +39,9 @@ def try_key( |
39 | 39 |
/, |
40 | 40 |
*, |
41 | 41 |
deterministic_signature_class: ( |
42 |
- tests.SSHTestKeyDeterministicSignatureClass |
|
43 |
- ) = tests.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
44 |
-) -> tests.SSHTestKey | None: |
|
42 |
+ tests.data.SSHTestKeyDeterministicSignatureClass |
|
43 |
+ ) = tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
44 |
+) -> tests.data.SSHTestKey | None: |
|
45 | 45 |
"""Query a signature and derived passphrase for the named key, if possible. |
46 | 46 |
|
47 | 47 |
Args: |
... | ... |
@@ -66,7 +66,7 @@ def try_key( |
66 | 66 |
`None` if no such signature augmentation could be performed. |
67 | 67 |
|
68 | 68 |
""" # noqa: E501,RUF002 |
69 |
- key = tests.ALL_KEYS[keyname] |
|
69 |
+ key = tests.data.ALL_KEYS[keyname] |
|
70 | 70 |
if not vault.Vault.is_suitable_ssh_key(key.public_key_data, client=client): |
71 | 71 |
return None |
72 | 72 |
signature: bytes |
... | ... |
@@ -82,18 +82,18 @@ def try_key( |
82 | 82 |
return None |
83 | 83 |
expected_signatures = dict(key.expected_signatures) |
84 | 84 |
signature_class = ( |
85 |
- tests.SSHTestKeyDeterministicSignatureClass.SPEC |
|
85 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
86 | 86 |
if vault.Vault.is_suitable_ssh_key(key.public_key_data) |
87 | 87 |
else deterministic_signature_class |
88 | 88 |
) |
89 | 89 |
expected_signatures[signature_class] = ( |
90 |
- tests.SSHTestKeyDeterministicSignature( |
|
90 |
+ tests.data.SSHTestKeyDeterministicSignature( |
|
91 | 91 |
signature=signature, |
92 | 92 |
derived_passphrase=derived_passphrase, |
93 | 93 |
signature_class=signature_class, |
94 | 94 |
) |
95 | 95 |
) |
96 |
- return tests.SSHTestKey( |
|
96 |
+ return tests.data.SSHTestKey( |
|
97 | 97 |
public_key=key.public_key, |
98 | 98 |
public_key_data=key.public_key_data, |
99 | 99 |
private_key=key.private_key, |
... | ... |
@@ -102,7 +102,7 @@ def try_key( |
102 | 102 |
) |
103 | 103 |
|
104 | 104 |
|
105 |
-def format_key(key: tests.SSHTestKey) -> str: |
|
105 |
+def format_key(key: tests.data.SSHTestKey) -> str: |
|
106 | 106 |
"""Return a formatted SSH test key.""" |
107 | 107 |
ascii_printables = range(32, 127) |
108 | 108 |
ascii_whitespace = {ord(" "), ord("\n"), ord("\t"), ord("\r"), ord("\f")} |
... | ... |
@@ -145,7 +145,7 @@ def format_key(key: tests.SSHTestKey) -> str: |
145 | 145 |
]) |
146 | 146 |
if ( |
147 | 147 |
sig.signature_class |
148 |
- != tests.SSHTestKeyDeterministicSignatureClass.SPEC |
|
148 |
+ != tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
149 | 149 |
): |
150 | 150 |
expected_signature_lines.append( |
151 | 151 |
f" signature_class={sig.signature_class!s},\n" |
... | ... |
@@ -168,16 +168,16 @@ def main(argv: list[str] | None = None) -> None: |
168 | 168 |
"--rfc-6979", |
169 | 169 |
action="store_const", |
170 | 170 |
dest="deterministic_signature_class", |
171 |
- const=tests.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
172 |
- default=tests.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
171 |
+ const=tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
172 |
+ default=tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
173 | 173 |
help="assume RFC 6979 signatures for deterministic DSA", |
174 | 174 |
) |
175 | 175 |
group.add_argument( |
176 | 176 |
"--pageant-068-080", |
177 | 177 |
action="store_const", |
178 | 178 |
dest="deterministic_signature_class", |
179 |
- const=tests.SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
180 |
- default=tests.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
179 |
+ const=tests.data.SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
180 |
+ default=tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
181 | 181 |
help="assume Pageant 0.68-0.80 signatures for deterministic DSA", |
182 | 182 |
) |
183 | 183 |
ap.add_argument( |
... | ... |
@@ -189,7 +189,7 @@ def main(argv: list[str] | None = None) -> None: |
189 | 189 |
) |
190 | 190 |
args = ap.parse_args(args=argv) |
191 | 191 |
if not args.keynames: |
192 |
- args.keynames = list(tests.ALL_KEYS.keys()) |
|
192 |
+ args.keynames = list(tests.data.ALL_KEYS.keys()) |
|
193 | 193 |
with ssh_agent.SSHAgentClient.ensure_agent_subcontext() as client: |
194 | 194 |
for keyname in args.keynames: |
195 | 195 |
key = try_key( |
... | ... |
@@ -2,3120 +2,53 @@ |
2 | 2 |
# |
3 | 3 |
# SPDX-License-Identifier: Zlib |
4 | 4 |
|
5 |
-from __future__ import annotations |
|
5 |
+"""The `derivepassphrase` test suite. |
|
6 | 6 |
|
7 |
-import base64 |
|
8 |
-import contextlib |
|
9 |
-import copy |
|
10 |
-import enum |
|
11 |
-import errno |
|
12 |
-import importlib.util |
|
13 |
-import json |
|
14 |
-import logging |
|
15 |
-import os |
|
16 |
-import pathlib |
|
17 |
-import re |
|
18 |
-import shlex |
|
19 |
-import stat |
|
20 |
-import sys |
|
21 |
-import tempfile |
|
22 |
-import types |
|
23 |
-import zipfile |
|
24 |
-from typing import TYPE_CHECKING, TypedDict |
|
7 |
+Overview |
|
8 |
+======== |
|
25 | 9 |
|
26 |
-import click.testing |
|
27 |
-import hypothesis |
|
28 |
-import pytest |
|
29 |
-from hypothesis import strategies |
|
30 |
-from typing_extensions import NamedTuple, assert_never, overload |
|
10 |
+The `derivepassphrase` test suite contains testing support code and the |
|
11 |
+actual unit- and integration-test functions. |
|
31 | 12 |
|
32 |
-from derivepassphrase import _types, cli, ssh_agent, vault |
|
33 |
-from derivepassphrase._internals import cli_helpers, cli_machinery |
|
34 |
-from derivepassphrase.ssh_agent import socketprovider |
|
13 |
+Layout (test functions) |
|
14 |
+======================= |
|
35 | 15 |
|
36 |
-__all__ = () |
|
16 |
+`derivepassphrase` uses `pytest`, `coverage` and `hypothesis` for |
|
17 |
+testing. Tests are auto-discovered using `pytest`'s test collection |
|
18 |
+machinery and configuration system, and may use `hypothesis` for |
|
19 |
+parametrization. |
|
37 | 20 |
|
38 |
-if TYPE_CHECKING: |
|
39 |
- import socket |
|
40 |
- from collections.abc import Callable, Iterator, Mapping, Sequence |
|
41 |
- from contextlib import AbstractContextManager |
|
42 |
- from typing import IO, NotRequired |
|
21 |
+Layout (support code) |
|
22 |
+===================== |
|
43 | 23 |
|
44 |
- from typing_extensions import Any, Buffer, Self |
|
24 |
+The testing support code is divided into five modules in two |
|
25 |
+subpackages: `tests.data`, `tests.data.callables`, `tests.machinery`, |
|
26 |
+`tests.machinery.pytest` and `tests.machinery.hypothesis`. They have |
|
27 |
+strict import requirements: any module in this list may only import data |
|
28 |
+and functionality from modules earlier in this list. |
|
45 | 29 |
|
30 |
+ * The package `tests.data` includes static test data, types, and |
|
31 |
+ associated lightweight machinery (think: accessors, categorization |
|
32 |
+ functions, and the like). |
|
46 | 33 |
|
47 |
-class SSHTestKeyDeterministicSignatureClass(str, enum.Enum): |
|
48 |
- """The class of a deterministic signature from an SSH test key. |
|
34 |
+ * The package `tests.data.callables` includes functions that operate |
|
35 |
+ on test data, or that alternatively implement stubbed versions of |
|
36 |
+ proper `derivepassphrase` functionality. This includes test |
|
37 |
+ doubles, if they do not depend on the presence of certain test |
|
38 |
+ machinery. Data is only included in this module (instead of in |
|
39 |
+ `tests.data`) if it depends on the functions in this module. |
|
49 | 40 |
|
50 |
- Attributes: |
|
51 |
- SPEC: |
|
52 |
- A deterministic signature directly implied by the |
|
53 |
- specification of the signature algorithm. |
|
54 |
- RFC_6979: |
|
55 |
- A deterministic signature as specified by RFC 6979. Only |
|
56 |
- used with DSA and ECDSA keys (that aren't also EdDSA keys). |
|
57 |
- Pageant_068_080: |
|
58 |
- A deterministic signature as specified by Pageant 0.68. |
|
59 |
- Only used with DSA and ECDSA keys (that aren't also EdDSA |
|
60 |
- keys), and only used with Pageant from 0.68 up to and |
|
61 |
- including 0.80. |
|
41 |
+ * The package `tests.machinery` includes data and functions that is |
|
42 |
+ not specific to any test system, but which nonetheless is of little |
|
43 |
+ value outside of such a system. This includes "fakes", i.e. |
|
44 |
+ reasonably complete reimplementations of existing `derivepassphrase` |
|
45 |
+ functionality, for comparison or "switching out" purposes. |
|
62 | 46 |
|
63 |
- Usage of this signature class together with an ECDSA NIST |
|
64 |
- P-521 key [turned out to leak enough information per |
|
65 |
- signature to quickly compromise the entire private key |
|
66 |
- (CVE-2024-31497)][PUTTY_CVE_2024_31497], so newer Pageant |
|
67 |
- versions abandon this signature class in favor of RFC 6979. |
|
47 |
+ * The package `tests.machinery.pytest` includes `pytest`-specific data |
|
48 |
+ and functions, such as marks and parametrization sets. |
|
68 | 49 |
|
69 |
- [PUTTY_CVE_2024_31497]: https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/vuln-p521-bias.html |
|
50 |
+ * The package `tests.machinery.hypothesis` includes |
|
51 |
+ `hypothesis`-specific data and functions, such as `hypothesis` |
|
52 |
+ strategies. |
|
70 | 53 |
|
71 | 54 |
""" |
72 |
- |
|
73 |
- SPEC = enum.auto() |
|
74 |
- """""" |
|
75 |
- RFC_6979 = enum.auto() |
|
76 |
- """""" |
|
77 |
- Pageant_068_080 = enum.auto() |
|
78 |
- """""" |
|
79 |
- |
|
80 |
- |
|
81 |
-class SSHTestKeyDeterministicSignature(NamedTuple): |
|
82 |
- """An SSH test key deterministic signature. |
|
83 |
- |
|
84 |
- Attributes: |
|
85 |
- signature: |
|
86 |
- The binary signature of the [vault UUID][vault.Vault.UUID] |
|
87 |
- under this signature class. |
|
88 |
- derived_passphrase: |
|
89 |
- The equivalent master passphrase derived from this |
|
90 |
- signature. |
|
91 |
- signature_class: |
|
92 |
- The [signature |
|
93 |
- class][SSHTestKeyDeterministicSignatureClass]. |
|
94 |
- |
|
95 |
- """ |
|
96 |
- |
|
97 |
- signature: bytes |
|
98 |
- """""" |
|
99 |
- derived_passphrase: bytes |
|
100 |
- """""" |
|
101 |
- signature_class: SSHTestKeyDeterministicSignatureClass = ( |
|
102 |
- SSHTestKeyDeterministicSignatureClass.SPEC |
|
103 |
- ) |
|
104 |
- """""" |
|
105 |
- |
|
106 |
- |
|
107 |
-class SSHTestKey(NamedTuple): |
|
108 |
- """An SSH test key. |
|
109 |
- |
|
110 |
- Attributes: |
|
111 |
- public_key: |
|
112 |
- The SSH public key string, as used e.g. by OpenSSH's |
|
113 |
- `authorized_keys` file. Includes a comment. |
|
114 |
- public_key_data: |
|
115 |
- The SSH protocol wire format of the public key. |
|
116 |
- private_key: |
|
117 |
- A base64 encoded representation of the private key, in |
|
118 |
- OpenSSH's v1 private key format. |
|
119 |
- private_key_blob: |
|
120 |
- The SSH protocol wire format of the private key. |
|
121 |
- expected_signatures: |
|
122 |
- A mapping of deterministic signature classes to the |
|
123 |
- expected, deterministic signature (of that class) of the |
|
124 |
- vault UUID for this key, together with the respective |
|
125 |
- "equivalent master passphrase" derived from this signature. |
|
126 |
- |
|
127 |
- """ |
|
128 |
- |
|
129 |
- public_key: bytes |
|
130 |
- """""" |
|
131 |
- public_key_data: bytes |
|
132 |
- """""" |
|
133 |
- private_key: bytes |
|
134 |
- """""" |
|
135 |
- private_key_blob: bytes |
|
136 |
- """""" |
|
137 |
- expected_signatures: Mapping[ |
|
138 |
- SSHTestKeyDeterministicSignatureClass, SSHTestKeyDeterministicSignature |
|
139 |
- ] |
|
140 |
- """""" |
|
141 |
- |
|
142 |
- def is_suitable( |
|
143 |
- self, |
|
144 |
- *, |
|
145 |
- client: ssh_agent.SSHAgentClient | None = None, |
|
146 |
- ) -> bool: |
|
147 |
- """Return if this key is suitable for use with vault. |
|
148 |
- |
|
149 |
- Args: |
|
150 |
- client: |
|
151 |
- An optional SSH agent client to check for additional |
|
152 |
- deterministic key types. If not given, assume no such |
|
153 |
- types. |
|
154 |
- |
|
155 |
- """ |
|
156 |
- return vault.Vault.is_suitable_ssh_key( |
|
157 |
- self.public_key_data, client=client |
|
158 |
- ) |
|
159 |
- |
|
160 |
- |
|
161 |
-class ValidationSettings(NamedTuple): |
|
162 |
- """Validation settings for [`VaultTestConfig`][]s. |
|
163 |
- |
|
164 |
- Attributes: |
|
165 |
- allow_unknown_settings: |
|
166 |
- See [`_types.validate_vault_config`][]. |
|
167 |
- |
|
168 |
- """ |
|
169 |
- |
|
170 |
- allow_unknown_settings: bool |
|
171 |
- """""" |
|
172 |
- |
|
173 |
- |
|
174 |
-class VaultTestConfig(NamedTuple): |
|
175 |
- """A (not necessarily valid) sample vault config, plus metadata. |
|
176 |
- |
|
177 |
- Attributes: |
|
178 |
- config: |
|
179 |
- The actual configuration object. Usually a [`dict`][]. |
|
180 |
- comment: |
|
181 |
- An explanatory comment for what is wrong with this config, |
|
182 |
- or empty if the config is valid. This is intended as |
|
183 |
- a debugging message to be shown to the user (e.g. when an |
|
184 |
- assertion fails), not as an error message to |
|
185 |
- programmatically match against. |
|
186 |
- validation_settings: |
|
187 |
- See [`_types.validate_vault_config`][]. |
|
188 |
- |
|
189 |
- """ |
|
190 |
- |
|
191 |
- config: Any |
|
192 |
- """""" |
|
193 |
- comment: str |
|
194 |
- """""" |
|
195 |
- validation_settings: ValidationSettings | None |
|
196 |
- """""" |
|
197 |
- |
|
198 |
- |
|
199 |
-TEST_CONFIGS: list[VaultTestConfig] = [ |
|
200 |
- VaultTestConfig(None, "not a dict", None), |
|
201 |
- VaultTestConfig({}, "missing required keys", None), |
|
202 |
- VaultTestConfig( |
|
203 |
- {"global": None, "services": {}}, "bad config value: global", None |
|
204 |
- ), |
|
205 |
- VaultTestConfig( |
|
206 |
- {"global": {"key": 123}, "services": {}}, |
|
207 |
- "bad config value: global.key", |
|
208 |
- None, |
|
209 |
- ), |
|
210 |
- VaultTestConfig( |
|
211 |
- {"global": {"phrase": "abc", "key": "..."}, "services": {}}, |
|
212 |
- "", |
|
213 |
- None, |
|
214 |
- ), |
|
215 |
- VaultTestConfig({"services": None}, "bad config value: services", None), |
|
216 |
- VaultTestConfig( |
|
217 |
- {"services": {"1": {}, 2: {}}}, 'bad config value: services."2"', None |
|
218 |
- ), |
|
219 |
- VaultTestConfig( |
|
220 |
- {"services": {"1": {}, "2": 2}}, 'bad config value: services."2"', None |
|
221 |
- ), |
|
222 |
- VaultTestConfig( |
|
223 |
- {"services": {"sv": {"notes": ["sentinel", "list"]}}}, |
|
224 |
- "bad config value: services.sv.notes", |
|
225 |
- None, |
|
226 |
- ), |
|
227 |
- VaultTestConfig( |
|
228 |
- {"services": {"sv": {"notes": "blah blah blah"}}}, "", None |
|
229 |
- ), |
|
230 |
- VaultTestConfig( |
|
231 |
- {"services": {"sv": {"length": "200"}}}, |
|
232 |
- "bad config value: services.sv.length", |
|
233 |
- None, |
|
234 |
- ), |
|
235 |
- VaultTestConfig( |
|
236 |
- {"services": {"sv": {"length": 0.5}}}, |
|
237 |
- "bad config value: services.sv.length", |
|
238 |
- None, |
|
239 |
- ), |
|
240 |
- VaultTestConfig( |
|
241 |
- {"services": {"sv": {"length": ["sentinel", "list"]}}}, |
|
242 |
- "bad config value: services.sv.length", |
|
243 |
- None, |
|
244 |
- ), |
|
245 |
- VaultTestConfig( |
|
246 |
- {"services": {"sv": {"length": -10}}}, |
|
247 |
- "bad config value: services.sv.length", |
|
248 |
- None, |
|
249 |
- ), |
|
250 |
- VaultTestConfig( |
|
251 |
- {"services": {"sv": {"lower": "10"}}}, |
|
252 |
- "bad config value: services.sv.lower", |
|
253 |
- None, |
|
254 |
- ), |
|
255 |
- VaultTestConfig( |
|
256 |
- {"services": {"sv": {"upper": -10}}}, |
|
257 |
- "bad config value: services.sv.upper", |
|
258 |
- None, |
|
259 |
- ), |
|
260 |
- VaultTestConfig( |
|
261 |
- {"services": {"sv": {"number": ["sentinel", "list"]}}}, |
|
262 |
- "bad config value: services.sv.number", |
|
263 |
- None, |
|
264 |
- ), |
|
265 |
- VaultTestConfig( |
|
266 |
- { |
|
267 |
- "global": {"phrase": "my secret phrase"}, |
|
268 |
- "services": {"sv": {"length": 10}}, |
|
269 |
- }, |
|
270 |
- "", |
|
271 |
- None, |
|
272 |
- ), |
|
273 |
- VaultTestConfig( |
|
274 |
- {"services": {"sv": {"length": 10, "phrase": "..."}}}, "", None |
|
275 |
- ), |
|
276 |
- VaultTestConfig( |
|
277 |
- {"services": {"sv": {"length": 10, "key": "..."}}}, "", None |
|
278 |
- ), |
|
279 |
- VaultTestConfig( |
|
280 |
- {"services": {"sv": {"upper": 10, "key": "..."}}}, "", None |
|
281 |
- ), |
|
282 |
- VaultTestConfig( |
|
283 |
- {"services": {"sv": {"phrase": "abc", "key": "..."}}}, "", None |
|
284 |
- ), |
|
285 |
- VaultTestConfig( |
|
286 |
- { |
|
287 |
- "global": {"phrase": "abc"}, |
|
288 |
- "services": {"sv": {"phrase": "abc", "length": 10}}, |
|
289 |
- }, |
|
290 |
- "", |
|
291 |
- None, |
|
292 |
- ), |
|
293 |
- VaultTestConfig( |
|
294 |
- { |
|
295 |
- "global": {"key": "..."}, |
|
296 |
- "services": {"sv": {"phrase": "abc", "length": 10}}, |
|
297 |
- }, |
|
298 |
- "", |
|
299 |
- None, |
|
300 |
- ), |
|
301 |
- VaultTestConfig( |
|
302 |
- { |
|
303 |
- "global": {"key": "..."}, |
|
304 |
- "services": {"sv": {"phrase": "abc", "key": "...", "length": 10}}, |
|
305 |
- }, |
|
306 |
- "", |
|
307 |
- None, |
|
308 |
- ), |
|
309 |
- VaultTestConfig( |
|
310 |
- { |
|
311 |
- "global": {"key": "..."}, |
|
312 |
- "services": { |
|
313 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
314 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
315 |
- }, |
|
316 |
- }, |
|
317 |
- "", |
|
318 |
- None, |
|
319 |
- ), |
|
320 |
- VaultTestConfig( |
|
321 |
- { |
|
322 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
323 |
- "services": { |
|
324 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
325 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
326 |
- }, |
|
327 |
- }, |
|
328 |
- "", |
|
329 |
- None, |
|
330 |
- ), |
|
331 |
- VaultTestConfig( |
|
332 |
- { |
|
333 |
- "global": {"key": "...", "unicode_normalization_form": True}, |
|
334 |
- "services": {}, |
|
335 |
- }, |
|
336 |
- "bad config value: global.unicode_normalization_form", |
|
337 |
- None, |
|
338 |
- ), |
|
339 |
- VaultTestConfig( |
|
340 |
- { |
|
341 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
342 |
- "services": { |
|
343 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
344 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
345 |
- }, |
|
346 |
- }, |
|
347 |
- "", |
|
348 |
- ValidationSettings(True), |
|
349 |
- ), |
|
350 |
- VaultTestConfig( |
|
351 |
- { |
|
352 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
353 |
- "services": { |
|
354 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
355 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
356 |
- }, |
|
357 |
- }, |
|
358 |
- "extension/unknown key: .global.unicode_normalization_form", |
|
359 |
- ValidationSettings(False), |
|
360 |
- ), |
|
361 |
- VaultTestConfig( |
|
362 |
- { |
|
363 |
- "global": {"key": "...", "unknown_key": True}, |
|
364 |
- "services": { |
|
365 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
366 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
367 |
- }, |
|
368 |
- }, |
|
369 |
- "", |
|
370 |
- ValidationSettings(True), |
|
371 |
- ), |
|
372 |
- VaultTestConfig( |
|
373 |
- { |
|
374 |
- "global": {"key": "...", "unknown_key": True}, |
|
375 |
- "services": { |
|
376 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
377 |
- "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
378 |
- }, |
|
379 |
- }, |
|
380 |
- "unknown key: .global.unknown_key", |
|
381 |
- ValidationSettings(False), |
|
382 |
- ), |
|
383 |
- VaultTestConfig( |
|
384 |
- { |
|
385 |
- "global": {"key": "..."}, |
|
386 |
- "services": { |
|
387 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
388 |
- "sv2": { |
|
389 |
- "length": 10, |
|
390 |
- "repeat": 1, |
|
391 |
- "lower": 1, |
|
392 |
- "unknown_key": True, |
|
393 |
- }, |
|
394 |
- }, |
|
395 |
- }, |
|
396 |
- "unknown key: .services.sv2.unknown_key", |
|
397 |
- ValidationSettings(False), |
|
398 |
- ), |
|
399 |
- VaultTestConfig( |
|
400 |
- { |
|
401 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
402 |
- "services": { |
|
403 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
404 |
- "sv2": { |
|
405 |
- "length": 10, |
|
406 |
- "repeat": 1, |
|
407 |
- "lower": 1, |
|
408 |
- "unknown_key": True, |
|
409 |
- }, |
|
410 |
- }, |
|
411 |
- }, |
|
412 |
- "", |
|
413 |
- ValidationSettings(True), |
|
414 |
- ), |
|
415 |
- VaultTestConfig( |
|
416 |
- { |
|
417 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
418 |
- "services": { |
|
419 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
420 |
- "sv2": { |
|
421 |
- "length": 10, |
|
422 |
- "repeat": 1, |
|
423 |
- "lower": 1, |
|
424 |
- "unknown_key": True, |
|
425 |
- }, |
|
426 |
- }, |
|
427 |
- }, |
|
428 |
- "", |
|
429 |
- ValidationSettings(True), |
|
430 |
- ), |
|
431 |
- VaultTestConfig( |
|
432 |
- { |
|
433 |
- "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
434 |
- "services": { |
|
435 |
- "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
436 |
- "sv2": { |
|
437 |
- "length": 10, |
|
438 |
- "repeat": 1, |
|
439 |
- "lower": 1, |
|
440 |
- "unknown_key": True, |
|
441 |
- }, |
|
442 |
- }, |
|
443 |
- }, |
|
444 |
- "", |
|
445 |
- ValidationSettings(True), |
|
446 |
- ), |
|
447 |
-] |
|
448 |
-"""The master list of test configurations for vault.""" |
|
449 |
- |
|
450 |
- |
|
451 |
-def is_valid_test_config(conf: VaultTestConfig, /) -> bool: |
|
452 |
- """Return true if the test config is valid. |
|
453 |
- |
|
454 |
- Args: |
|
455 |
- conf: The test config to check. |
|
456 |
- |
|
457 |
- """ |
|
458 |
- return not conf.comment and conf.validation_settings in { |
|
459 |
- None, |
|
460 |
- (True,), |
|
461 |
- } |
|
462 |
- |
|
463 |
- |
|
464 |
-def _test_config_ids(val: VaultTestConfig) -> Any: # pragma: no cover |
|
465 |
- """pytest id function for VaultTestConfig objects.""" |
|
466 |
- assert isinstance(val, VaultTestConfig) |
|
467 |
- return val[1] or (val[0], val[1], val[2]) |
|
468 |
- |
|
469 |
- |
|
470 |
-@strategies.composite |
|
471 |
-def vault_full_service_config(draw: strategies.DrawFn) -> dict[str, int]: |
|
472 |
- """Hypothesis strategy for full vault service configurations. |
|
473 |
- |
|
474 |
- Returns a sample configuration with restrictions on length, repeat |
|
475 |
- count, and all character classes, while ensuring the settings are |
|
476 |
- not obviously unsatisfiable. |
|
477 |
- |
|
478 |
- Args: |
|
479 |
- draw: |
|
480 |
- The `draw` function, as provided for by hypothesis. |
|
481 |
- |
|
482 |
- """ |
|
483 |
- repeat = draw(strategies.integers(min_value=0, max_value=10)) |
|
484 |
- lower = draw(strategies.integers(min_value=0, max_value=10)) |
|
485 |
- upper = draw(strategies.integers(min_value=0, max_value=10)) |
|
486 |
- number = draw(strategies.integers(min_value=0, max_value=10)) |
|
487 |
- space = draw(strategies.integers(min_value=0, max_value=repeat)) |
|
488 |
- dash = draw(strategies.integers(min_value=0, max_value=10)) |
|
489 |
- symbol = draw(strategies.integers(min_value=0, max_value=10)) |
|
490 |
- length = draw( |
|
491 |
- strategies.integers( |
|
492 |
- min_value=max(1, lower + upper + number + space + dash + symbol), |
|
493 |
- max_value=70, |
|
494 |
- ) |
|
495 |
- ) |
|
496 |
- hypothesis.assume(lower + upper + number + dash + symbol > 0) |
|
497 |
- hypothesis.assume(lower + upper + number + space + symbol > 0) |
|
498 |
- hypothesis.assume(repeat >= space) |
|
499 |
- return { |
|
500 |
- "lower": lower, |
|
501 |
- "upper": upper, |
|
502 |
- "number": number, |
|
503 |
- "space": space, |
|
504 |
- "dash": dash, |
|
505 |
- "symbol": symbol, |
|
506 |
- "repeat": repeat, |
|
507 |
- "length": length, |
|
508 |
- } |
|
509 |
- |
|
510 |
- |
|
511 |
-def is_smudgable_vault_test_config(conf: VaultTestConfig) -> bool: |
|
512 |
- """Check whether this vault test config can be effectively smudged. |
|
513 |
- |
|
514 |
- A "smudged" test config is one where falsy values (in the JavaScript |
|
515 |
- sense) can be replaced by other falsy values without changing the |
|
516 |
- meaning of the config. |
|
517 |
- |
|
518 |
- Args: |
|
519 |
- conf: A test config to check. |
|
520 |
- |
|
521 |
- Returns: |
|
522 |
- True if the test config can be smudged, False otherwise. |
|
523 |
- |
|
524 |
- """ |
|
525 |
- config = conf.config |
|
526 |
- return bool( |
|
527 |
- isinstance(config, dict) |
|
528 |
- and ("global" not in config or isinstance(config["global"], dict)) |
|
529 |
- and ("services" in config and isinstance(config["services"], dict)) |
|
530 |
- and all(isinstance(x, dict) for x in config["services"].values()) |
|
531 |
- and (config["services"] or config.get("global")) |
|
532 |
- ) |
|
533 |
- |
|
534 |
- |
|
535 |
-@strategies.composite |
|
536 |
-def smudged_vault_test_config( |
|
537 |
- draw: strategies.DrawFn, |
|
538 |
- config: Any = strategies.sampled_from(TEST_CONFIGS).filter( # noqa: B008 |
|
539 |
- is_smudgable_vault_test_config |
|
540 |
- ), |
|
541 |
-) -> Any: |
|
542 |
- """Hypothesis strategy to replace falsy values with other falsy values. |
|
543 |
- |
|
544 |
- Uses [`_types.js_truthiness`][] internally, which is tested |
|
545 |
- separately by |
|
546 |
- [`tests.test_derivepassphrase_types.test_100_js_truthiness`][]. |
|
547 |
- |
|
548 |
- Args: |
|
549 |
- draw: |
|
550 |
- The `draw` function, as provided for by hypothesis. |
|
551 |
- config: |
|
552 |
- A strategy which generates [`VaultTestConfig`][] objects. |
|
553 |
- |
|
554 |
- Returns: |
|
555 |
- A new [`VaultTestConfig`][] where some falsy values have been |
|
556 |
- replaced or added. |
|
557 |
- |
|
558 |
- """ |
|
559 |
- |
|
560 |
- falsy = (None, False, 0, 0.0, "", float("nan")) |
|
561 |
- falsy_no_str = (None, False, 0, 0.0, float("nan")) |
|
562 |
- falsy_no_zero = (None, False, "", float("nan")) |
|
563 |
- conf = draw(config) |
|
564 |
- hypothesis.assume(is_smudgable_vault_test_config(conf)) |
|
565 |
- obj = copy.deepcopy(conf.config) |
|
566 |
- services: list[dict[str, Any]] = list(obj["services"].values()) |
|
567 |
- if "global" in obj: |
|
568 |
- services.append(obj["global"]) |
|
569 |
- assert all(isinstance(x, dict) for x in services), ( |
|
570 |
- "is_smudgable_vault_test_config guard failed to " |
|
571 |
- "ensure each settings dict is a dict" |
|
572 |
- ) |
|
573 |
- for service in services: |
|
574 |
- for key in ("phrase",): |
|
575 |
- value = service.get(key) |
|
576 |
- if not _types.js_truthiness(value) and value != "": |
|
577 |
- service[key] = draw(strategies.sampled_from(falsy_no_str)) |
|
578 |
- for key in ( |
|
579 |
- "notes", |
|
580 |
- "key", |
|
581 |
- "length", |
|
582 |
- "repeat", |
|
583 |
- ): |
|
584 |
- value = service.get(key) |
|
585 |
- if not _types.js_truthiness(value): |
|
586 |
- service[key] = draw(strategies.sampled_from(falsy)) |
|
587 |
- for key in ( |
|
588 |
- "lower", |
|
589 |
- "upper", |
|
590 |
- "number", |
|
591 |
- "space", |
|
592 |
- "dash", |
|
593 |
- "symbol", |
|
594 |
- ): |
|
595 |
- value = service.get(key) |
|
596 |
- if not _types.js_truthiness(value) and value != 0: |
|
597 |
- service[key] = draw(strategies.sampled_from(falsy_no_zero)) |
|
598 |
- hypothesis.assume(obj != conf.config) |
|
599 |
- return VaultTestConfig(obj, conf.comment, conf.validation_settings) |
|
600 |
- |
|
601 |
- |
|
602 |
-class KnownSSHAgent(str, enum.Enum): |
|
603 |
- """Known SSH agents. |
|
604 |
- |
|
605 |
- Attributes: |
|
606 |
- UNKNOWN (str): |
|
607 |
- Not a known agent, or not known statically. |
|
608 |
- Pageant (str): |
|
609 |
- The agent from Simon Tatham's PuTTY suite. |
|
610 |
- OpenSSHAgent (str): |
|
611 |
- The agent from OpenBSD's OpenSSH suite. |
|
612 |
- StubbedSSHAgent (str): |
|
613 |
- The stubbed, fake agent pseudo-socket defined in this test |
|
614 |
- suite. |
|
615 |
- |
|
616 |
- """ |
|
617 |
- |
|
618 |
- UNKNOWN = "(unknown)" |
|
619 |
- """""" |
|
620 |
- Pageant = "Pageant" |
|
621 |
- """""" |
|
622 |
- OpenSSHAgent = "OpenSSHAgent" |
|
623 |
- """""" |
|
624 |
- StubbedSSHAgent = "StubbedSSHAgent" |
|
625 |
- """""" |
|
626 |
- |
|
627 |
- |
|
628 |
-class SpawnedSSHAgentInfo(NamedTuple): |
|
629 |
- """Info about a spawned SSH agent, as provided by some fixtures. |
|
630 |
- |
|
631 |
- Differs from [`RunningSSHAgentInfo`][] in that this info object |
|
632 |
- already provides a functional client connected to the agent, but not |
|
633 |
- the address. |
|
634 |
- |
|
635 |
- Attributes: |
|
636 |
- agent_type: |
|
637 |
- The agent's type. |
|
638 |
- client: |
|
639 |
- An SSH agent client connected to this agent. |
|
640 |
- isolated: |
|
641 |
- Whether this agent was spawned specifically for this test |
|
642 |
- suite, with attempts to isolate it from the user. If false, |
|
643 |
- then the user may be interacting with the agent externally, |
|
644 |
- meaning e.g. keys other than the test keys may be visible in |
|
645 |
- this agent. |
|
646 |
- |
|
647 |
- """ |
|
648 |
- |
|
649 |
- agent_type: KnownSSHAgent |
|
650 |
- """""" |
|
651 |
- client: ssh_agent.SSHAgentClient |
|
652 |
- """""" |
|
653 |
- isolated: bool |
|
654 |
- """""" |
|
655 |
- |
|
656 |
- |
|
657 |
-class RunningSSHAgentInfo(NamedTuple): |
|
658 |
- """Info about a running SSH agent, as provided by some fixtures. |
|
659 |
- |
|
660 |
- Differs from [`SpawnedSSHAgentInfo`][] in that this info object |
|
661 |
- provides only an address of the agent, not a functional client |
|
662 |
- already connected to it. The running SSH agent may or may not be |
|
663 |
- isolated. |
|
664 |
- |
|
665 |
- Attributes: |
|
666 |
- socket: |
|
667 |
- A socket address to connect to the agent. |
|
668 |
- agent_type: |
|
669 |
- The agent's type. |
|
670 |
- |
|
671 |
- """ |
|
672 |
- |
|
673 |
- socket: str | type[_types.SSHAgentSocket] |
|
674 |
- """""" |
|
675 |
- agent_type: KnownSSHAgent |
|
676 |
- """""" |
|
677 |
- |
|
678 |
- def require_external_address(self) -> str: # pragma: no cover |
|
679 |
- if not isinstance(self.socket, str): |
|
680 |
- pytest.skip( |
|
681 |
- reason="This test requires a real, externally resolvable " |
|
682 |
- "address for the SSH agent socket." |
|
683 |
- ) |
|
684 |
- return self.socket |
|
685 |
- |
|
686 |
- |
|
687 |
-ALL_KEYS: Mapping[str, SSHTestKey] = { |
|
688 |
- "ed25519": SSHTestKey( |
|
689 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
690 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW |
|
691 |
-QyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdgAAAKDweO7H8Hju |
|
692 |
-xwAAAAtzc2gtZWQyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdg |
|
693 |
-AAAEAbM/A869nkWZbe2tp3Dm/L6gitvmpH/aRZt8sBII3ExYF4gWgm1gJIXw//Mkhv5MEw |
|
694 |
-idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC |
|
695 |
------END OPENSSH PRIVATE KEY----- |
|
696 |
-""", |
|
697 |
- private_key_blob=bytes.fromhex(""" |
|
698 |
- 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
699 |
- 00 00 00 20 |
|
700 |
- 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
701 |
- 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
702 |
- 00 00 00 40 |
|
703 |
- 1b 33 f0 3c eb d9 e4 59 96 de da da 77 0e 6f cb |
|
704 |
- ea 08 ad be 6a 47 fd a4 59 b7 cb 01 20 8d c4 c5 |
|
705 |
- 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
706 |
- 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
707 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 74 |
|
708 |
- 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
709 |
-"""), |
|
710 |
- public_key=rb"""ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIF4gWgm1gJIXw//Mkhv5MEwidwcakUGCekJD/vCEml2 test key without passphrase |
|
711 |
-""", |
|
712 |
- public_key_data=bytes.fromhex(""" |
|
713 |
- 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
714 |
- 00 00 00 20 |
|
715 |
- 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
716 |
- 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
717 |
-"""), |
|
718 |
- expected_signatures={ |
|
719 |
- SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
720 |
- signature=bytes.fromhex(""" |
|
721 |
- 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
722 |
- 00 00 00 40 |
|
723 |
- f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86 |
|
724 |
- 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd |
|
725 |
- 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c |
|
726 |
- 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02 |
|
727 |
-"""), |
|
728 |
- derived_passphrase=rb"""8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==""", |
|
729 |
- ), |
|
730 |
- }, |
|
731 |
- ), |
|
732 |
- # Currently only supported by PuTTY (which is deficient in other |
|
733 |
- # niceties of the SSH agent and the agent's client). |
|
734 |
- "ed448": SSHTestKey( |
|
735 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
736 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz |
|
737 |
-c2gtZWQ0NDgAAAA54vZy009Wu8wExjvEb3hqtLz1GO/+d5vmGUbErWQ4AUO9mYLT |
|
738 |
-zHJHc2m4s+yWzP29Cc3EcxizLG8AAAAA8BdhfCcXYXwnAAAACXNzaC1lZDQ0OAAA |
|
739 |
-ADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM |
|
740 |
-/b0JzcRzGLMsbwAAAAByM7GIMRvWJB3YD6SIpAF2uudX4ozZe0X917wPwiBrs373 |
|
741 |
-9TM1n94Nib6hrxGNmCk2iBQDe2KALPgA4vZy009Wu8wExjvEb3hqtLz1GO/+d5vm |
|
742 |
-GUbErWQ4AUO9mYLTzHJHc2m4s+yWzP29Cc3EcxizLG8AAAAAG3Rlc3Qga2V5IHdp |
|
743 |
-dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ== |
|
744 |
------END OPENSSH PRIVATE KEY----- |
|
745 |
-""", |
|
746 |
- private_key_blob=bytes.fromhex(""" |
|
747 |
- 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
748 |
- 00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04 |
|
749 |
- c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
750 |
- 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
751 |
- b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
752 |
- 00 00 00 72 33 b1 |
|
753 |
- 88 31 1b d6 24 1d d8 0f a4 88 a4 01 76 ba e7 57 |
|
754 |
- e2 8c d9 7b 45 fd d7 bc 0f c2 20 6b b3 7e f7 f5 |
|
755 |
- 33 35 9f de 0d 89 be a1 af 11 8d 98 29 36 88 14 |
|
756 |
- 03 7b 62 80 2c f8 00 e2 f6 72 d3 4f 56 bb cc 04 |
|
757 |
- c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
758 |
- 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
759 |
- b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
760 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
761 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
762 |
-"""), |
|
763 |
- public_key=rb"""ssh-ed448 AAAACXNzaC1lZDQ0OAAAADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM/b0JzcRzGLMsbwA= test key without passphrase |
|
764 |
-""", |
|
765 |
- public_key_data=bytes.fromhex(""" |
|
766 |
- 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
767 |
- 00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04 |
|
768 |
- c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
769 |
- 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
770 |
- b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
771 |
- """), |
|
772 |
- expected_signatures={ |
|
773 |
- SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
774 |
- signature=bytes.fromhex(""" |
|
775 |
- 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
776 |
- 00 00 00 72 06 86 |
|
777 |
- f4 64 a4 a6 ba d9 c3 22 c4 93 49 99 fc 11 de 67 |
|
778 |
- 97 08 f2 d8 b7 3c 2c 13 e7 c5 1c 1e 92 a6 0e d8 |
|
779 |
- 2f 6d 81 03 82 00 e3 72 e4 32 6d 72 d2 6d 32 84 |
|
780 |
- 3f cc a9 1e 57 2c 00 9a b3 99 de 45 da ce 2e d1 |
|
781 |
- db e5 89 f3 35 be 24 58 90 c6 ca 04 f0 db 88 80 |
|
782 |
- db bd 77 7c 80 20 7f 3a 48 61 f6 1f ae a9 5e 53 |
|
783 |
- 7b e0 9d 93 1e ea dc eb b5 cd 56 4c ea 8f 08 00 |
|
784 |
-"""), |
|
785 |
- derived_passphrase=rb"""Bob0ZKSmutnDIsSTSZn8Ed5nlwjy2Lc8LBPnxRwekqYO2C9tgQOCAONy5DJtctJtMoQ/zKkeVywAmrOZ3kXazi7R2+WJ8zW+JFiQxsoE8NuIgNu9d3yAIH86SGH2H66pXlN74J2THurc67XNVkzqjwgA""", |
|
786 |
- ), |
|
787 |
- }, |
|
788 |
- ), |
|
789 |
- "rsa": SSHTestKey( |
|
790 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
791 |
-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
-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
-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
-G4wYDuqxshwelraB/L3e0zhD7fjYHF8IbFsqGlFHWEwOtlfhhfbxJsTGguLm4A8/gdEJD5 |
|
795 |
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-6qRUJ+fVZkqRGEaI3oWyWKTOhcCIYImtONvb0LOv/HQ2H2AXCoeqjST1qr/xSuljBtcB8u |
|
810 |
-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
-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
-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
-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
-+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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-vC2EAD91xexdorIA5BgXU+qltBqzzBVzVtF7+jOZOjfzOlaTX9I5I5veyeTaTxZj1XXUzi |
|
825 |
-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
-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 |
|
827 |
------END OPENSSH PRIVATE KEY----- |
|
828 |
-""", |
|
829 |
- private_key_blob=bytes.fromhex(""" |
|
830 |
- 00 00 00 07 73 73 68 2d 72 73 61 |
|
831 |
- 00 00 01 81 00 |
|
832 |
- b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa a0 |
|
833 |
- f5 60 98 38 04 be 22 3c 4d f5 e6 1c aa 97 dc 99 |
|
834 |
- 08 a8 8d cb b2 f5 7b 22 37 fc 58 27 53 bd e1 2a |
|
835 |
- ec 29 2e 66 12 26 b6 c4 79 d8 03 84 54 0a 14 f2 |
|
836 |
- 60 d7 0c da fe 9c 9e 70 50 fc df d5 42 1a 46 30 |
|
837 |
- de d2 57 17 5b 84 a5 17 4d 61 42 c1 b8 c1 80 ee |
|
838 |
- ab 1b 21 c1 e9 6b 68 1f cb dd ed 33 84 3e df 8d |
|
839 |
- 81 c5 f0 86 c5 b2 a1 a5 14 75 84 c0 eb 65 7e 18 |
|
840 |
- 5f 6f 12 6c 4c 68 2e 2e 6e 00 f3 f8 1d 10 90 f9 |
|
841 |
- da b9 2a 0d c6 69 21 70 87 b4 96 c2 cd 6f 5a 42 |
|
842 |
- 95 9c b3 f3 c3 c3 9c a5 c2 5f f7 74 1e e3 7f 1c |
|
843 |
- 9f ac 6c f8 3b 74 2b ce ca a1 58 4b 22 a7 de e6 |
|
844 |
- 3a 6c 25 ea a0 87 6e af 15 f3 1f 73 bb cf 43 fc |
|
845 |
- 7f 9b 1b 98 a3 ab 18 62 8d b8 dc 55 45 b7 95 97 |
|
846 |
- f0 ff de e0 cb d2 7a ac 6d f5 1c d6 ab e4 7f 06 |
|
847 |
- c9 c2 d3 17 0b 82 15 ea 43 99 31 29 36 04 0b 2a |
|
848 |
- 87 bc 78 f2 04 3e ae 16 1e 11 54 a2 f0 5d c2 5a |
|
849 |
- ce 07 25 0a 2a ac 6b 7b 2b d6 b7 98 24 a5 30 11 |
|
850 |
- cf ef 4b b7 c3 a3 04 ed eb a6 a2 bc b1 95 4a 1f |
|
851 |
- 7b 04 dd d7 b6 44 93 37 57 d3 c9 76 66 52 b3 66 |
|
852 |
- fc 10 52 b7 3e c5 06 76 53 0f 33 da 67 d6 e9 38 |
|
853 |
- b8 82 2d 29 60 66 a2 83 b2 9e e0 fc 2e 5e 9a 3f |
|
854 |
- 0b 96 00 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31 |
|
855 |
- d6 5e 24 63 40 9c 72 d4 18 b9 01 b1 cc 39 68 8f |
|
856 |
- 00 00 00 03 01 00 01 |
|
857 |
- 00 00 01 7f |
|
858 |
- 70 d5 58 4f e3 a6 e3 1f 7e 48 8b 5c cf 96 ce |
|
859 |
- 07 21 88 3a 3c 96 50 7f 1f f6 b4 63 9e be 48 2e |
|
860 |
- ec 1a 6c 80 e0 bc 56 95 1b ec 4c d4 ed 61 58 00 |
|
861 |
- ea cc c9 d2 11 c9 27 1d 19 13 60 4b 2a 60 fc a4 |
|
862 |
- 02 dc 45 be 33 85 12 1e f4 ea a4 54 27 e7 d5 66 |
|
863 |
- 4a 91 18 46 88 de 85 b2 58 a4 ce 85 c0 88 60 89 |
|
864 |
- ad 38 db db d0 b3 af fc 74 36 1f 60 17 0a 87 aa |
|
865 |
- 8d 24 f5 aa bf f1 4a e9 63 06 d7 01 f2 ec 31 b3 |
|
866 |
- 71 2a 68 ed 72 53 b4 28 66 90 dc 31 7f 69 94 7e |
|
867 |
- d1 99 cf 67 35 f6 5c 82 b9 e4 b4 f9 36 75 e5 f6 |
|
868 |
- f9 8f c0 e6 dc dd 56 01 b5 21 ea 98 a7 a0 81 30 |
|
869 |
- b3 4c ba 12 f4 38 f7 43 7a 18 01 a0 3b a3 c7 ea |
|
870 |
- e1 91 9d 60 a6 8d 70 71 42 c8 33 de 0d 2f b5 52 |
|
871 |
- 1c 13 d7 d0 b8 21 f5 3f b7 8f 51 d5 3f e6 27 30 |
|
872 |
- 0b e1 7c eb df 72 d2 e5 6c 9d 3a f9 36 06 44 03 |
|
873 |
- 2e 17 62 cf 08 06 4c fe 6d 05 13 20 2e ad 18 16 |
|
874 |
- c4 08 cb 5e 6f b8 92 9d 86 00 95 e5 c3 cd 9d 78 |
|
875 |
- 39 10 f8 ff 1c 1c d4 fa b3 d6 47 47 96 12 cb 79 |
|
876 |
- 69 67 7e 79 f7 86 4c 38 b4 7c 2a a5 6f 86 02 46 |
|
877 |
- 17 a8 bd d3 5f 41 88 5f 75 48 3c 63 13 eb 29 e9 |
|
878 |
- d3 46 7c df 16 fb ae 2d 1d d0 ef d9 ad 0c b2 8f |
|
879 |
- bc 42 3c b1 ee c4 e9 8b ef 84 8c b5 3c 23 2e f7 |
|
880 |
- fd ee 01 f2 a6 d7 33 28 5e c8 0f d4 51 f8 0c b4 |
|
881 |
- d2 92 86 1e ac 2e 5a c3 f6 1d 92 9b d4 45 4e e1 |
|
882 |
- 00 00 00 c0 |
|
883 |
- 26 1d ee ed 67 e7 8e 18 4a 65 86 c0 5b 84 ec 3c |
|
884 |
- d5 c3 32 b0 74 fe de f1 c8 04 20 16 69 28 ec 97 |
|
885 |
- c4 4b 2a c9 48 b8 35 70 ef 90 30 e8 83 80 3e f3 |
|
886 |
- b9 0f 51 4a 93 48 29 d3 ec 9c 89 da e6 97 a1 f7 |
|
887 |
- bf 66 56 36 f6 1e 09 e2 92 3c 89 10 29 81 a4 b7 |
|
888 |
- 70 64 a8 57 ec c9 c8 be 02 b0 e6 59 d0 5c 76 e8 |
|
889 |
- 12 d5 82 6b 53 79 f2 5e df b2 bc f0 6e 2f 69 15 |
|
890 |
- 4b 94 a7 d7 52 85 73 5f 7e 69 54 0a 30 d1 b1 52 |
|
891 |
- db 2b 90 ba fa 26 88 43 20 96 15 82 bc 99 ce 9d |
|
892 |
- a4 d9 08 a0 f2 9f f9 78 3e 10 90 88 15 0a 08 4e |
|
893 |
- a7 ae df 41 9c d7 e3 3e 7d d6 5c da 08 6a d1 be |
|
894 |
- 97 2e d8 61 fe e1 c4 af 2f 1b eb c3 39 2a 09 4c |
|
895 |
- 00 00 00 c1 00 |
|
896 |
- f0 d6 2f 88 75 cb cf 57 fd ee fe af 4e fe 4c cb |
|
897 |
- a4 a3 b7 b5 b4 fc f6 c7 35 ce 18 d9 b0 33 63 0c |
|
898 |
- 01 a5 b8 da fa 9f 7f 22 ab f5 d8 45 9b bb 51 32 |
|
899 |
- fd 04 6f 84 80 55 5f 21 45 7b 5e c6 13 e5 ef 81 |
|
900 |
- 3d fd 77 55 2f 78 af 36 7b 99 a0 ac 3a 55 0b 5f |
|
901 |
- e5 5d 30 ed b3 06 e7 07 22 87 9c f5 15 25 45 9a |
|
902 |
- df 07 6d 41 d9 6e d8 18 c8 5b a6 86 b9 94 dd e5 |
|
903 |
- 28 94 3b 69 f1 e8 75 76 54 32 9d 1c 4c 56 ce 99 |
|
904 |
- bc c9 81 2c ce db f3 44 ec 18 55 c2 6b dc 53 34 |
|
905 |
- c3 24 63 0c 2a ec 41 3b ac 3d f5 82 83 29 12 a6 |
|
906 |
- d0 f3 4b 14 3c f6 ea b5 a6 f4 b7 4d 4b 63 15 7d |
|
907 |
- 5b 8f 31 3a 73 4f 6e 87 40 6e 29 15 a8 1e ab d7 |
|
908 |
- 00 00 00 c1 00 |
|
909 |
- bc d1 05 77 59 17 c9 1b 48 16 b9 31 35 01 55 34 |
|
910 |
- f3 80 a4 26 d4 2e fb da 02 7c 4f 4d 43 24 45 d9 |
|
911 |
- b1 5b 4e bf b9 94 0b 5f e6 fd bc ba 1e 4e 2d 3f |
|
912 |
- 2b c3 06 e1 a3 f6 11 ea e0 de dd 3e a1 ae b4 76 |
|
913 |
- f5 ab 99 f4 00 3b a8 42 34 56 ec 15 f0 e5 0b c2 |
|
914 |
- d8 40 03 f7 5c 5e c5 da 2b 20 0e 41 81 75 3e aa |
|
915 |
- 5b 41 ab 3c c1 57 35 6d 17 bf a3 39 93 a3 7f 33 |
|
916 |
- a5 69 35 fd 23 92 39 bd ec 9e 4d a4 f1 66 3d 57 |
|
917 |
- 5d 4c e2 6e d0 4d 74 c1 09 26 9e e2 7e e7 18 9a |
|
918 |
- 86 00 03 01 3b 2b e5 65 59 a8 03 10 ad b2 f0 cb |
|
919 |
- 5e f7 2f 44 f8 dd 2e 3b 68 fe 87 ce 6c 42 df d4 |
|
920 |
- 21 bd a2 13 fb e1 72 00 60 a7 ad 78 d9 69 d2 09 |
|
921 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
922 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
923 |
-"""), |
|
924 |
- 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 |
|
925 |
-""", |
|
926 |
- public_key_data=bytes.fromhex(""" |
|
927 |
- 00 00 00 07 73 73 68 2d 72 73 61 |
|
928 |
- 00 00 00 03 01 00 01 |
|
929 |
- 00 00 01 81 00 |
|
930 |
- b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa a0 |
|
931 |
- f5 60 98 38 04 be 22 3c 4d f5 e6 1c aa 97 dc 99 |
|
932 |
- 08 a8 8d cb b2 f5 7b 22 37 fc 58 27 53 bd e1 2a |
|
933 |
- ec 29 2e 66 12 26 b6 c4 79 d8 03 84 54 0a 14 f2 |
|
934 |
- 60 d7 0c da fe 9c 9e 70 50 fc df d5 42 1a 46 30 |
|
935 |
- de d2 57 17 5b 84 a5 17 4d 61 42 c1 b8 c1 80 ee |
|
936 |
- ab 1b 21 c1 e9 6b 68 1f cb dd ed 33 84 3e df 8d |
|
937 |
- 81 c5 f0 86 c5 b2 a1 a5 14 75 84 c0 eb 65 7e 18 |
|
938 |
- 5f 6f 12 6c 4c 68 2e 2e 6e 00 f3 f8 1d 10 90 f9 |
|
939 |
- da b9 2a 0d c6 69 21 70 87 b4 96 c2 cd 6f 5a 42 |
|
940 |
- 95 9c b3 f3 c3 c3 9c a5 c2 5f f7 74 1e e3 7f 1c |
|
941 |
- 9f ac 6c f8 3b 74 2b ce ca a1 58 4b 22 a7 de e6 |
|
942 |
- 3a 6c 25 ea a0 87 6e af 15 f3 1f 73 bb cf 43 fc |
|
943 |
- 7f 9b 1b 98 a3 ab 18 62 8d b8 dc 55 45 b7 95 97 |
|
944 |
- f0 ff de e0 cb d2 7a ac 6d f5 1c d6 ab e4 7f 06 |
|
945 |
- c9 c2 d3 17 0b 82 15 ea 43 99 31 29 36 04 0b 2a |
|
946 |
- 87 bc 78 f2 04 3e ae 16 1e 11 54 a2 f0 5d c2 5a |
|
947 |
- ce 07 25 0a 2a ac 6b 7b 2b d6 b7 98 24 a5 30 11 |
|
948 |
- cf ef 4b b7 c3 a3 04 ed eb a6 a2 bc b1 95 4a 1f |
|
949 |
- 7b 04 dd d7 b6 44 93 37 57 d3 c9 76 66 52 b3 66 |
|
950 |
- fc 10 52 b7 3e c5 06 76 53 0f 33 da 67 d6 e9 38 |
|
951 |
- b8 82 2d 29 60 66 a2 83 b2 9e e0 fc 2e 5e 9a 3f |
|
952 |
- 0b 96 00 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31 |
|
953 |
- d6 5e 24 63 40 9c 72 d4 18 b9 01 b1 cc 39 68 8f |
|
954 |
-"""), |
|
955 |
- expected_signatures={ |
|
956 |
- SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
957 |
- signature=bytes.fromhex(""" |
|
958 |
- 00 00 00 07 73 73 68 2d 72 73 61 |
|
959 |
- 00 00 01 80 |
|
960 |
- a2 10 7c 2e f6 bb 53 a8 74 2a a1 19 99 ad 81 be |
|
961 |
- 79 9c ed d6 9d 09 4e 6e c5 18 48 33 90 77 99 68 |
|
962 |
- f7 9e 03 5a cd 4e 18 eb 89 7d 85 a2 ee ae 4a 92 |
|
963 |
- f6 6f ce b9 fe 86 7f 2a 6b 31 da 6e 1a fe a2 a5 |
|
964 |
- 88 b8 44 7f a1 76 73 b3 ec 75 b5 d0 a6 b9 15 97 |
|
965 |
- 65 09 13 7d 94 21 d1 fb 5d 0f 8b 23 04 77 c2 c3 |
|
966 |
- 55 22 b1 a0 09 8a f5 38 2a d6 7f 1b 87 29 a0 25 |
|
967 |
- d3 25 6f cb 64 61 07 98 dc 14 c5 84 f8 92 24 5e |
|
968 |
- 50 11 6b 49 e5 f0 cc 29 cb 29 a9 19 d8 a7 71 1f |
|
969 |
- 91 0b 05 b1 01 4b c2 5f 00 a5 b6 21 bf f8 2c 9d |
|
970 |
- 67 9b 47 3b 0a 49 6b 79 2d fc 1d ec 0c b0 e5 27 |
|
971 |
- 22 d5 a9 f8 d3 c3 f9 df 48 68 e9 fb ef 3c dc 26 |
|
972 |
- bf cf ea 29 43 01 a6 e3 c5 51 95 f4 66 6d 8a 55 |
|
973 |
- e2 47 ec e8 30 45 4c ae 47 e7 c9 a4 21 8b 64 ba |
|
974 |
- b6 88 f6 21 f8 73 b9 cb 11 a1 78 75 92 c6 5a e5 |
|
975 |
- 64 fe ed 42 d9 95 99 e6 2b 6f 3c 16 3c 28 74 a4 |
|
976 |
- 72 2f 0d 3f 2c 33 67 aa 35 19 8e e7 b5 11 2f b3 |
|
977 |
- f7 6a c5 02 e2 6f a3 42 e3 62 19 99 03 ea a5 20 |
|
978 |
- e7 a1 e3 bc c8 06 a3 b5 7c d6 76 5d df 6f 60 46 |
|
979 |
- 83 2a 08 00 d6 d3 d9 a4 c1 41 8c f8 60 56 45 81 |
|
980 |
- da 3b a2 16 1f 9e 4e 75 83 17 da c3 53 c3 3e 19 |
|
981 |
- a4 1b bc d2 29 b8 78 61 2b 78 e6 b1 52 b0 d5 ec |
|
982 |
- de 69 2c 48 62 d9 fd d1 9b 6b b0 49 db d3 ff 38 |
|
983 |
- e7 10 d9 2d ce 9f 0d 5e 09 7b 37 d2 7b c3 bf ce |
|
984 |
-"""), |
|
985 |
- derived_passphrase=rb"""ohB8Lva7U6h0KqEZma2Bvnmc7dadCU5uxRhIM5B3mWj3ngNazU4Y64l9haLurkqS9m/Ouf6GfyprMdpuGv6ipYi4RH+hdnOz7HW10Ka5FZdlCRN9lCHR+10PiyMEd8LDVSKxoAmK9Tgq1n8bhymgJdMlb8tkYQeY3BTFhPiSJF5QEWtJ5fDMKcspqRnYp3EfkQsFsQFLwl8ApbYhv/gsnWebRzsKSWt5Lfwd7Ayw5Sci1an408P530ho6fvvPNwmv8/qKUMBpuPFUZX0Zm2KVeJH7OgwRUyuR+fJpCGLZLq2iPYh+HO5yxGheHWSxlrlZP7tQtmVmeYrbzwWPCh0pHIvDT8sM2eqNRmO57URL7P3asUC4m+jQuNiGZkD6qUg56HjvMgGo7V81nZd329gRoMqCADW09mkwUGM+GBWRYHaO6IWH55OdYMX2sNTwz4ZpBu80im4eGEreOaxUrDV7N5pLEhi2f3Rm2uwSdvT/zjnENktzp8NXgl7N9J7w7/O""", |
|
986 |
- ), |
|
987 |
- }, |
|
988 |
- ), |
|
989 |
- "dsa1024": SSHTestKey( |
|
990 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
991 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH |
|
992 |
-NzAAAAgQC7KAZXqBGNVLBQPrcMYAoNW54BhD8aIhe7BDWYzJcsaMt72VKSkguZ8+XR7nRa |
|
993 |
-0C/ZsBi+uJp0dpxy9ZMTOWX4u5YPMeQcXEdGExZIfimGqSOAsy6fCld2IfJZJZExcCmhe9 |
|
994 |
-Ssjsd3YSAPJRluOXFQc95MZoR5hMwlIDD8QzrE7QAAABUA99nOZOgd7aHMVGoXpUEBcn7H |
|
995 |
-ossAAACALr2Ag3hxM3rKdxzVUw8fX0VVPXO+3+Kr8hGe0Kc/7NwVaBVL1GQ8fenBuWynpA |
|
996 |
-UbH0wo3h1wkB/8hX6p+S8cnu5rIBlUuVNwLw/bIYohK98LfqTYK/V+g6KD+8m34wvEiXZm |
|
997 |
-qywY54n2bksch1Nqvj/tNpLzExSx/XS0kSM1aigAAACAbQNRPcVEuGDrEcf+xg5tgAejPX |
|
998 |
-BPXr/Jss+Chk64km3mirMYjAWyWYtVcgT+7hOYxtYRin8LyMLqKRmqa0Q5UrvDfChgLhvs |
|
999 |
-G9YSb/Mpw5qm8PiHSafwhkaz/te3+8hKogqoe7sd+tCF06IpJr5k70ACiNtRGqssNF8Elr |
|
1000 |
-l1efYAAAH4swlfVrMJX1YAAAAHc3NoLWRzcwAAAIEAuygGV6gRjVSwUD63DGAKDVueAYQ/ |
|
1001 |
-GiIXuwQ1mMyXLGjLe9lSkpILmfPl0e50WtAv2bAYvriadHaccvWTEzll+LuWDzHkHFxHRh |
|
1002 |
-MWSH4phqkjgLMunwpXdiHyWSWRMXApoXvUrI7Hd2EgDyUZbjlxUHPeTGaEeYTMJSAw/EM6 |
|
1003 |
-xO0AAAAVAPfZzmToHe2hzFRqF6VBAXJ+x6LLAAAAgC69gIN4cTN6yncc1VMPH19FVT1zvt |
|
1004 |
-/iq/IRntCnP+zcFWgVS9RkPH3pwblsp6QFGx9MKN4dcJAf/IV+qfkvHJ7uayAZVLlTcC8P |
|
1005 |
-2yGKISvfC36k2Cv1foOig/vJt+MLxIl2ZqssGOeJ9m5LHIdTar4/7TaS8xMUsf10tJEjNW |
|
1006 |
-ooAAAAgG0DUT3FRLhg6xHH/sYObYAHoz1wT16/ybLPgoZOuJJt5oqzGIwFslmLVXIE/u4T |
|
1007 |
-mMbWEYp/C8jC6ikZqmtEOVK7w3woYC4b7BvWEm/zKcOapvD4h0mn8IZGs/7Xt/vISqIKqH |
|
1008 |
-u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW |
|
1009 |
-0gAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UBAgMEBQYH |
|
1010 |
------END OPENSSH PRIVATE KEY----- |
|
1011 |
-""", |
|
1012 |
- private_key_blob=bytes.fromhex(""" |
|
1013 |
- 00 00 00 07 73 73 68 2d 64 73 73 |
|
1014 |
- 00 00 00 81 00 |
|
1015 |
- bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d |
|
1016 |
- 5b 9e 01 84 3f 1a 22 17 bb 04 35 98 cc 97 2c 68 |
|
1017 |
- cb 7b d9 52 92 92 0b 99 f3 e5 d1 ee 74 5a d0 2f |
|
1018 |
- d9 b0 18 be b8 9a 74 76 9c 72 f5 93 13 39 65 f8 |
|
1019 |
- bb 96 0f 31 e4 1c 5c 47 46 13 16 48 7e 29 86 a9 |
|
1020 |
- 23 80 b3 2e 9f 0a 57 76 21 f2 59 25 91 31 70 29 |
|
1021 |
- a1 7b d4 ac 8e c7 77 61 20 0f 25 19 6e 39 71 50 |
|
1022 |
- 73 de 4c 66 84 79 84 cc 25 20 30 fc 43 3a c4 ed |
|
1023 |
- 00 00 00 15 00 f7 d9 ce 64 |
|
1024 |
- e8 1d ed a1 cc 54 6a 17 a5 41 01 72 7e c7 a2 cb |
|
1025 |
- 00 00 00 80 |
|
1026 |
- 2e bd 80 83 78 71 33 7a ca 77 1c d5 53 0f 1f 5f |
|
1027 |
- 45 55 3d 73 be df e2 ab f2 11 9e d0 a7 3f ec dc |
|
1028 |
- 15 68 15 4b d4 64 3c 7d e9 c1 b9 6c a7 a4 05 1b |
|
1029 |
- 1f 4c 28 de 1d 70 90 1f fc 85 7e a9 f9 2f 1c 9e |
|
1030 |
- ee 6b 20 19 54 b9 53 70 2f 0f db 21 8a 21 2b df |
|
1031 |
- 0b 7e a4 d8 2b f5 7e 83 a2 83 fb c9 b7 e3 0b c4 |
|
1032 |
- 89 76 66 ab 2c 18 e7 89 f6 6e 4b 1c 87 53 6a be |
|
1033 |
- 3f ed 36 92 f3 13 14 b1 fd 74 b4 91 23 35 6a 28 |
|
1034 |
- 00 00 00 80 |
|
1035 |
- 6d 03 51 3d c5 44 b8 60 eb 11 c7 fe c6 0e 6d 80 |
|
1036 |
- 07 a3 3d 70 4f 5e bf c9 b2 cf 82 86 4e b8 92 6d |
|
1037 |
- e6 8a b3 18 8c 05 b2 59 8b 55 72 04 fe ee 13 98 |
|
1038 |
- c6 d6 11 8a 7f 0b c8 c2 ea 29 19 aa 6b 44 39 52 |
|
1039 |
- bb c3 7c 28 60 2e 1b ec 1b d6 12 6f f3 29 c3 9a |
|
1040 |
- a6 f0 f8 87 49 a7 f0 86 46 b3 fe d7 b7 fb c8 4a |
|
1041 |
- a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef |
|
1042 |
- 40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6 |
|
1043 |
- 00 00 00 15 00 c9 1d f7 a7 |
|
1044 |
- 8f 81 09 f5 69 1e 86 97 49 6a d3 c1 96 a0 96 d2 |
|
1045 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
1046 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
1047 |
-"""), |
|
1048 |
- public_key=rb"""ssh-dss AAAAB3NzaC1kc3MAAACBALsoBleoEY1UsFA+twxgCg1bngGEPxoiF7sENZjMlyxoy3vZUpKSC5nz5dHudFrQL9mwGL64mnR2nHL1kxM5Zfi7lg8x5BxcR0YTFkh+KYapI4CzLp8KV3Yh8lklkTFwKaF71KyOx3dhIA8lGW45cVBz3kxmhHmEzCUgMPxDOsTtAAAAFQD32c5k6B3tocxUahelQQFyfseiywAAAIAuvYCDeHEzesp3HNVTDx9fRVU9c77f4qvyEZ7Qpz/s3BVoFUvUZDx96cG5bKekBRsfTCjeHXCQH/yFfqn5Lxye7msgGVS5U3AvD9shiiEr3wt+pNgr9X6DooP7ybfjC8SJdmarLBjnifZuSxyHU2q+P+02kvMTFLH9dLSRIzVqKAAAAIBtA1E9xUS4YOsRx/7GDm2AB6M9cE9ev8myz4KGTriSbeaKsxiMBbJZi1VyBP7uE5jG1hGKfwvIwuopGaprRDlSu8N8KGAuG+wb1hJv8ynDmqbw+IdJp/CGRrP+17f7yEqiCqh7ux360IXToikmvmTvQAKI21Eaqyw0XwSWuXV59g== test key without passphrase |
|
1049 |
-""", |
|
1050 |
- public_key_data=bytes.fromhex(""" |
|
1051 |
- 00 00 00 07 73 73 68 2d 64 73 73 |
|
1052 |
- 00 00 00 81 00 |
|
1053 |
- bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d |
|
1054 |
- 5b 9e 01 84 3f 1a 22 17 bb 04 35 98 cc 97 2c 68 |
|
1055 |
- cb 7b d9 52 92 92 0b 99 f3 e5 d1 ee 74 5a d0 2f |
|
1056 |
- d9 b0 18 be b8 9a 74 76 9c 72 f5 93 13 39 65 f8 |
|
1057 |
- bb 96 0f 31 e4 1c 5c 47 46 13 16 48 7e 29 86 a9 |
|
1058 |
- 23 80 b3 2e 9f 0a 57 76 21 f2 59 25 91 31 70 29 |
|
1059 |
- a1 7b d4 ac 8e c7 77 61 20 0f 25 19 6e 39 71 50 |
|
1060 |
- 73 de 4c 66 84 79 84 cc 25 20 30 fc 43 3a c4 ed |
|
1061 |
- 00 00 00 15 00 f7 d9 ce 64 |
|
1062 |
- e8 1d ed a1 cc 54 6a 17 a5 41 01 72 7e c7 a2 cb |
|
1063 |
- 00 00 00 80 |
|
1064 |
- 2e bd 80 83 78 71 33 7a ca 77 1c d5 53 0f 1f 5f |
|
1065 |
- 45 55 3d 73 be df e2 ab f2 11 9e d0 a7 3f ec dc |
|
1066 |
- 15 68 15 4b d4 64 3c 7d e9 c1 b9 6c a7 a4 05 1b |
|
1067 |
- 1f 4c 28 de 1d 70 90 1f fc 85 7e a9 f9 2f 1c 9e |
|
1068 |
- ee 6b 20 19 54 b9 53 70 2f 0f db 21 8a 21 2b df |
|
1069 |
- 0b 7e a4 d8 2b f5 7e 83 a2 83 fb c9 b7 e3 0b c4 |
|
1070 |
- 89 76 66 ab 2c 18 e7 89 f6 6e 4b 1c 87 53 6a be |
|
1071 |
- 3f ed 36 92 f3 13 14 b1 fd 74 b4 91 23 35 6a 28 |
|
1072 |
- 00 00 00 80 |
|
1073 |
- 6d 03 51 3d c5 44 b8 60 eb 11 c7 fe c6 0e 6d 80 |
|
1074 |
- 07 a3 3d 70 4f 5e bf c9 b2 cf 82 86 4e b8 92 6d |
|
1075 |
- e6 8a b3 18 8c 05 b2 59 8b 55 72 04 fe ee 13 98 |
|
1076 |
- c6 d6 11 8a 7f 0b c8 c2 ea 29 19 aa 6b 44 39 52 |
|
1077 |
- bb c3 7c 28 60 2e 1b ec 1b d6 12 6f f3 29 c3 9a |
|
1078 |
- a6 f0 f8 87 49 a7 f0 86 46 b3 fe d7 b7 fb c8 4a |
|
1079 |
- a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef |
|
1080 |
- 40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6 |
|
1081 |
-"""), |
|
1082 |
- expected_signatures={ |
|
1083 |
- SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
1084 |
- signature=bytes.fromhex(""" |
|
1085 |
- 00 00 00 07 73 73 68 2d 64 73 73 |
|
1086 |
- 00 00 00 28 11 5f 4d 13 c2 ee 61 97 |
|
1087 |
- 1e f6 23 14 3b 2b dd cf 06 c0 71 13 cc ac 34 19 |
|
1088 |
- ad 36 8d 79 aa 25 fb 5e 4f ea fe 6b 5b fa 57 42 |
|
1089 |
-"""), |
|
1090 |
- derived_passphrase=rb"""EV9NE8LuYZce9iMUOyvdzwbAcRPMrDQZrTaNeaol+15P6v5rW/pXQg==""", |
|
1091 |
- signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
1092 |
- ), |
|
1093 |
- SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
1094 |
- signature=bytes.fromhex(""" |
|
1095 |
- 00 00 00 07 73 73 68 2d 64 73 73 |
|
1096 |
- 00 00 00 28 0b f7 a8 ab 89 f5 b6 c4 |
|
1097 |
- 1c 9b 78 2c 46 35 69 e2 88 b7 eb 55 37 48 7f 6d |
|
1098 |
- 49 a1 e6 de 58 1a 04 eb e6 28 99 0e 3c fd 3b 48 |
|
1099 |
-"""), |
|
1100 |
- derived_passphrase=rb"""C/eoq4n1tsQcm3gsRjVp4oi361U3SH9tSaHm3lgaBOvmKJkOPP07SA==""", |
|
1101 |
- signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
1102 |
- ), |
|
1103 |
- }, |
|
1104 |
- ), |
|
1105 |
- "ecdsa256": SSHTestKey( |
|
1106 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
1107 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS |
|
1108 |
-1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTLbU0zDwsk2Dvp+VYIrsNVf5gWwz2S |
|
1109 |
-3SZ8TbxiQRkpnGSVqyIoHJOJc+NQItAa7xlJ/8Z6gfz57Z3apUkaMJm6AAAAuKeY+YinmP |
|
1110 |
-mIAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5 |
|
1111 |
-Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmb |
|
1112 |
-oAAAAhAKIl/3n0pKVIxpZkXTGtii782Qr4yIcvHdpxjO/QsIqKAAAAG3Rlc3Qga2V5IHdp |
|
1113 |
-dGhvdXQgcGFzc3BocmFzZQECAwQ= |
|
1114 |
------END OPENSSH PRIVATE KEY----- |
|
1115 |
-""", |
|
1116 |
- private_key_blob=bytes.fromhex(""" |
|
1117 |
- 00 00 00 13 65 63 64 |
|
1118 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
1119 |
- 00 00 00 08 6e 69 73 74 70 32 35 36 |
|
1120 |
- 00 00 00 41 04 |
|
1121 |
- cb 6d 4d 33 0f 0b 24 d8 3b e9 f9 56 08 ae c3 55 |
|
1122 |
- 7f 98 16 c3 3d 92 dd 26 7c 4d bc 62 41 19 29 9c |
|
1123 |
- 64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19 |
|
1124 |
- 49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba |
|
1125 |
- 00 00 00 21 00 |
|
1126 |
- a2 25 ff 79 f4 a4 a5 48 c6 96 64 5d 31 ad 8a 2e |
|
1127 |
- fc d9 0a f8 c8 87 2f 1d da 71 8c ef d0 b0 8a 8a |
|
1128 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
1129 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
1130 |
-"""), |
|
1131 |
- public_key=rb"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmbo= test key without passphrase |
|
1132 |
-""", |
|
1133 |
- public_key_data=bytes.fromhex(""" |
|
1134 |
- 00 00 00 13 65 63 64 |
|
1135 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
1136 |
- 00 00 00 08 6e 69 73 74 70 32 35 36 |
|
1137 |
- 00 00 00 41 04 |
|
1138 |
- cb 6d 4d 33 0f 0b 24 d8 3b e9 f9 56 08 ae c3 55 |
|
1139 |
- 7f 98 16 c3 3d 92 dd 26 7c 4d bc 62 41 19 29 9c |
|
1140 |
- 64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19 |
|
1141 |
- 49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba |
|
1142 |
-"""), |
|
1143 |
- expected_signatures={ |
|
1144 |
- SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
1145 |
- signature=bytes.fromhex(""" |
|
1146 |
- 00 00 00 13 65 63 64 |
|
1147 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
1148 |
- 00 00 00 49 |
|
1149 |
- 00 00 00 20 |
|
1150 |
- 22 ad 23 8a 9c 5d ca 4e ea 73 e7 29 77 ab a8 b2 |
|
1151 |
- 2e 01 d8 de 11 ae c9 b3 57 ce d5 84 9c 85 73 eb |
|
1152 |
- 00 00 00 21 00 |
|
1153 |
- 9b 1a cb dd 45 89 f0 37 95 9c a2 d8 ac c3 f7 71 |
|
1154 |
- 55 33 50 86 9e cb 3a 95 e4 68 80 1a 9d d6 d5 bc |
|
1155 |
-"""), |
|
1156 |
- derived_passphrase=rb"""AAAAICKtI4qcXcpO6nPnKXerqLIuAdjeEa7Js1fO1YSchXPrAAAAIQCbGsvdRYnwN5Wcotisw/dxVTNQhp7LOpXkaIAandbVvA==""", |
|
1157 |
- signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
1158 |
- ), |
|
1159 |
- SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
1160 |
- signature=bytes.fromhex(""" |
|
1161 |
- 00 00 00 13 65 63 64 |
|
1162 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
1163 |
- 00 00 00 49 |
|
1164 |
- 00 00 00 21 00 |
|
1165 |
- b7 9e 4f ec ec 9b 77 dd 12 d9 43 a2 f5 bf b5 34 |
|
1166 |
- 91 e0 89 44 e6 20 48 36 fa 75 22 77 86 38 de 21 |
|
1167 |
- 00 00 00 20 |
|
1168 |
- 3f d8 04 0f fa f5 bc d2 26 e0 4c 0c 77 5d 0e 08 |
|
1169 |
- ec 30 04 8e 42 58 41 96 f6 7e 4f d2 14 39 f4 87 |
|
1170 |
-"""), |
|
1171 |
- derived_passphrase=rb"""AAAAIQC3nk/s7Jt33RLZQ6L1v7U0keCJROYgSDb6dSJ3hjjeIQAAACA/2AQP+vW80ibgTAx3XQ4I7DAEjkJYQZb2fk/SFDn0hw==""", |
|
1172 |
- signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
1173 |
- ), |
|
1174 |
- }, |
|
1175 |
- ), |
|
1176 |
- "ecdsa384": SSHTestKey( |
|
1177 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
1178 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS |
|
1179 |
-1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSgkOjkAvq7v5vHuj3KBL4/EAWcn5hZ |
|
1180 |
-DyKcbyV0eBMGFq7hKXQlZqIahLVqeMR0QqmkxNJ2rly2VHcXneq3vZ+9fIsWCOdYk5WP3N |
|
1181 |
-ZPzv911Xn7wbEkC7QndD5zKlm4pBUAAADomhj+IZoY/iEAAAATZWNkc2Etc2hhMi1uaXN0 |
|
1182 |
-cDM4NAAAAAhuaXN0cDM4NAAAAGEEoJDo5AL6u7+bx7o9ygS+PxAFnJ+YWQ8inG8ldHgTBh |
|
1183 |
-au4Sl0JWaiGoS1anjEdEKppMTSdq5ctlR3F53qt72fvXyLFgjnWJOVj9zWT87/ddV5+8Gx |
|
1184 |
-JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B |
|
1185 |
-2OdqVOVRkwZWRROz0AAAAbdGVzdCBrZXkgd2l0aG91dCBwYXNzcGhyYXNlAQIDBA== |
|
1186 |
------END OPENSSH PRIVATE KEY----- |
|
1187 |
-""", |
|
1188 |
- private_key_blob=bytes.fromhex(""" |
|
1189 |
- 00 00 00 13 65 63 64 |
|
1190 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
1191 |
- 00 00 00 08 6e 69 73 74 70 33 38 34 |
|
1192 |
- 00 00 00 61 04 |
|
1193 |
- a0 90 e8 e4 02 fa bb bf 9b c7 ba 3d ca 04 be 3f |
|
1194 |
- 10 05 9c 9f 98 59 0f 22 9c 6f 25 74 78 13 06 16 |
|
1195 |
- ae e1 29 74 25 66 a2 1a 84 b5 6a 78 c4 74 42 a9 |
|
1196 |
- a4 c4 d2 76 ae 5c b6 54 77 17 9d ea b7 bd 9f bd |
|
1197 |
- 7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5 |
|
1198 |
- 79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15 |
|
1199 |
- 00 00 00 31 00 |
|
1200 |
- f9 b1 3c bc a7 e0 75 72 7f c3 84 e9 97 aa eb a2 |
|
1201 |
- d4 17 31 bc 04 aa cf 37 af 90 19 5b 42 80 5a ef |
|
1202 |
- 7c b4 e0 1d 8e 76 a5 4e 55 19 30 65 64 51 3b 3d |
|
1203 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
1204 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
1205 |
-"""), |
|
1206 |
- public_key=rb"""ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKCQ6OQC+ru/m8e6PcoEvj8QBZyfmFkPIpxvJXR4EwYWruEpdCVmohqEtWp4xHRCqaTE0nauXLZUdxed6re9n718ixYI51iTlY/c1k/O/3XVefvBsSQLtCd0PnMqWbikFQ== test key without passphrase |
|
1207 |
-""", |
|
1208 |
- public_key_data=bytes.fromhex(""" |
|
1209 |
- 00 00 00 13 65 63 64 |
|
1210 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
1211 |
- 00 00 00 08 6e 69 73 74 70 33 38 34 |
|
1212 |
- 00 00 00 61 04 |
|
1213 |
- a0 90 e8 e4 02 fa bb bf 9b c7 ba 3d ca 04 be 3f |
|
1214 |
- 10 05 9c 9f 98 59 0f 22 9c 6f 25 74 78 13 06 16 |
|
1215 |
- ae e1 29 74 25 66 a2 1a 84 b5 6a 78 c4 74 42 a9 |
|
1216 |
- a4 c4 d2 76 ae 5c b6 54 77 17 9d ea b7 bd 9f bd |
|
1217 |
- 7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5 |
|
1218 |
- 79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15 |
|
1219 |
-"""), |
|
1220 |
- expected_signatures={ |
|
1221 |
- SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
1222 |
- signature=bytes.fromhex(""" |
|
1223 |
- 00 00 00 13 65 63 64 |
|
1224 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
1225 |
- 00 00 00 68 |
|
1226 |
- 00 00 00 30 |
|
1227 |
- 78 e1 a8 f5 8c d2 7a 21 e5 a2 ca e6 d0 1a 19 f8 |
|
1228 |
- 3a 1c 39 7e 71 a0 e6 7e 93 83 49 95 05 01 d0 3e |
|
1229 |
- 23 22 cd 09 63 7f 7c 6c b0 97 44 6d 7e 48 39 87 |
|
1230 |
- 00 00 00 30 |
|
1231 |
- 10 ee 85 51 77 2b 91 2c e9 42 79 66 59 8a a2 c0 |
|
1232 |
- d2 c8 8a 8f 2f 8f 33 87 9e 12 54 e4 da 02 f9 e7 |
|
1233 |
- 95 f5 82 6f 82 2b 38 6d 6e 5d 17 15 ac 12 e7 62 |
|
1234 |
-"""), |
|
1235 |
- derived_passphrase=rb"""AAAAMHjhqPWM0noh5aLK5tAaGfg6HDl+caDmfpODSZUFAdA+IyLNCWN/fGywl0Rtfkg5hwAAADAQ7oVRdyuRLOlCeWZZiqLA0siKjy+PM4eeElTk2gL555X1gm+CKzhtbl0XFawS52I=""", |
|
1236 |
- signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
1237 |
- ), |
|
1238 |
- SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
1239 |
- signature=bytes.fromhex(""" |
|
1240 |
- 00 00 00 13 65 63 64 |
|
1241 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
1242 |
- 00 00 00 69 |
|
1243 |
- 00 00 00 30 |
|
1244 |
- 4b 3e b7 22 c2 87 77 6d e0 3e f5 05 75 36 b6 0f |
|
1245 |
- cd 9f a4 49 c7 48 ef 76 fd ea 4b 49 e3 b1 f2 22 |
|
1246 |
- d5 41 22 d7 96 b2 29 70 ff bb 81 97 27 e2 35 60 |
|
1247 |
- 00 00 00 31 00 |
|
1248 |
- c8 a4 d8 62 fe f2 a6 63 97 98 08 c7 39 24 b2 55 |
|
1249 |
- 0a b8 e7 79 ab a6 62 96 3e cc ea 73 e2 fb dc 46 |
|
1250 |
- d6 25 b9 c8 0c e8 3e 33 91 51 78 25 a8 c5 46 85 |
|
1251 |
-"""), |
|
1252 |
- derived_passphrase=rb"""AAAAMEs+tyLCh3dt4D71BXU2tg/Nn6RJx0jvdv3qS0njsfIi1UEi15ayKXD/u4GXJ+I1YAAAADEAyKTYYv7ypmOXmAjHOSSyVQq453mrpmKWPszqc+L73EbWJbnIDOg+M5FReCWoxUaF""", |
|
1253 |
- signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
1254 |
- ), |
|
1255 |
- }, |
|
1256 |
- ), |
|
1257 |
- "ecdsa521": SSHTestKey( |
|
1258 |
- private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
1259 |
-b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS |
|
1260 |
-1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQASVOdwDznmlcGqiLvFtYeVtrAEiVz |
|
1261 |
-iIfsL7jEM8Utu/m8WSkPFQtjwqdFw+WfZ0mi6qMbEFgi/ELzZSKVteCSbcMAhqAkOMFKiD |
|
1262 |
-u4bxvsM6bT02Ru7q2yT41ySyGhUD0QySBnI6Ckt/wnQ1TEpj8zDKiRErxs9e6QLGElNRkz |
|
1263 |
-LPMs+mMAAAEY2FXeh9hV3ocAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ |
|
1264 |
-AAAIUEAElTncA855pXBqoi7xbWHlbawBIlc4iH7C+4xDPFLbv5vFkpDxULY8KnRcPln2dJ |
|
1265 |
-ouqjGxBYIvxC82UilbXgkm3DAIagJDjBSog7uG8b7DOm09Nkbu6tsk+NckshoVA9EMkgZy |
|
1266 |
-OgpLf8J0NUxKY/MwyokRK8bPXukCxhJTUZMyzzLPpjAAAAQSFqUmKK7lGQzxT6GKZSLDju |
|
1267 |
-U3otwLYnuj+/5AdzuB/zotu95UdFv9I2DNXzd9E4WAyz6IqBBNcsMkxrzHAdqsYDAAAAG3 |
|
1268 |
-Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQ== |
|
1269 |
------END OPENSSH PRIVATE KEY----- |
|
1270 |
-""", |
|
1271 |
- private_key_blob=bytes.fromhex(""" |
|
1272 |
- 00 00 00 13 65 63 64 |
|
1273 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1274 |
- 00 00 00 08 6e 69 73 74 70 35 32 31 |
|
1275 |
- 00 00 00 85 04 00 49 53 9d |
|
1276 |
- c0 3c e7 9a 57 06 aa 22 ef 16 d6 1e 56 da c0 12 |
|
1277 |
- 25 73 88 87 ec 2f b8 c4 33 c5 2d bb f9 bc 59 29 |
|
1278 |
- 0f 15 0b 63 c2 a7 45 c3 e5 9f 67 49 a2 ea a3 1b |
|
1279 |
- 10 58 22 fc 42 f3 65 22 95 b5 e0 92 6d c3 00 86 |
|
1280 |
- a0 24 38 c1 4a 88 3b b8 6f 1b ec 33 a6 d3 d3 64 |
|
1281 |
- 6e ee ad b2 4f 8d 72 4b 21 a1 50 3d 10 c9 20 67 |
|
1282 |
- 23 a0 a4 b7 fc 27 43 54 c4 a6 3f 33 0c a8 91 12 |
|
1283 |
- bc 6c f5 ee 90 2c 61 25 35 19 33 2c f3 2c fa 63 |
|
1284 |
- 00 00 00 41 21 |
|
1285 |
- 6a 52 62 8a ee 51 90 cf 14 fa 18 a6 52 2c 38 ee |
|
1286 |
- 53 7a 2d c0 b6 27 ba 3f bf e4 07 73 b8 1f f3 a2 |
|
1287 |
- db bd e5 47 45 bf d2 36 0c d5 f3 77 d1 38 58 0c |
|
1288 |
- b3 e8 8a 81 04 d7 2c 32 4c 6b cc 70 1d aa c6 03 |
|
1289 |
- 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
1290 |
- 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
1291 |
-"""), |
|
1292 |
- public_key=rb"""ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABJU53APOeaVwaqIu8W1h5W2sASJXOIh+wvuMQzxS27+bxZKQ8VC2PCp0XD5Z9nSaLqoxsQWCL8QvNlIpW14JJtwwCGoCQ4wUqIO7hvG+wzptPTZG7urbJPjXJLIaFQPRDJIGcjoKS3/CdDVMSmPzMMqJESvGz17pAsYSU1GTMs8yz6Yw== test key without passphrase |
|
1293 |
-""", |
|
1294 |
- public_key_data=bytes.fromhex(""" |
|
1295 |
- 00 00 00 13 65 63 64 |
|
1296 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1297 |
- 00 00 00 08 6e 69 73 74 70 35 32 31 |
|
1298 |
- 00 00 00 85 04 00 49 53 9d |
|
1299 |
- c0 3c e7 9a 57 06 aa 22 ef 16 d6 1e 56 da c0 12 |
|
1300 |
- 25 73 88 87 ec 2f b8 c4 33 c5 2d bb f9 bc 59 29 |
|
1301 |
- 0f 15 0b 63 c2 a7 45 c3 e5 9f 67 49 a2 ea a3 1b |
|
1302 |
- 10 58 22 fc 42 f3 65 22 95 b5 e0 92 6d c3 00 86 |
|
1303 |
- a0 24 38 c1 4a 88 3b b8 6f 1b ec 33 a6 d3 d3 64 |
|
1304 |
- 6e ee ad b2 4f 8d 72 4b 21 a1 50 3d 10 c9 20 67 |
|
1305 |
- 23 a0 a4 b7 fc 27 43 54 c4 a6 3f 33 0c a8 91 12 |
|
1306 |
- bc 6c f5 ee 90 2c 61 25 35 19 33 2c f3 2c fa 63 |
|
1307 |
-"""), |
|
1308 |
- expected_signatures={ |
|
1309 |
- SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
1310 |
- signature=bytes.fromhex(""" |
|
1311 |
- 00 00 00 13 65 63 64 |
|
1312 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1313 |
- 00 00 00 8b |
|
1314 |
- 00 00 00 42 01 d8 |
|
1315 |
- ea c2 1e 55 c6 9e dd 4b 00 ed 1b 93 19 cc 9b 74 |
|
1316 |
- 27 44 c0 c0 e3 5b 3d 81 15 00 12 cc 07 89 54 97 |
|
1317 |
- ec 60 42 ad e6 40 c1 c6 5f c0 1b c3 0a 8e 58 6e |
|
1318 |
- da 3f a9 57 90 04 79 46 1d 48 bb 19 67 e9 65 19 |
|
1319 |
- 00 00 00 41 7d |
|
1320 |
- 58 e0 2e d7 86 2e 36 8c 1a 44 23 af 19 e7 51 97 |
|
1321 |
- bb fb 32 90 a1 35 bb 88 d7 b5 22 37 b3 99 ba e4 |
|
1322 |
- a7 9d 2d 56 14 0a f5 68 f5 cc 38 84 e9 b6 c6 71 |
|
1323 |
- 7a 3b 87 e7 7a b1 37 e7 1d e6 80 96 d1 a6 1e bc |
|
1324 |
-"""), |
|
1325 |
- derived_passphrase=rb"""AAAAQgHY6sIeVcae3UsA7RuTGcybdCdEwMDjWz2BFQASzAeJVJfsYEKt5kDBxl/AG8MKjlhu2j+pV5AEeUYdSLsZZ+llGQAAAEF9WOAu14YuNowaRCOvGedRl7v7MpChNbuI17UiN7OZuuSnnS1WFAr1aPXMOITptsZxejuH53qxN+cd5oCW0aYevA==""", |
|
1326 |
- signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
1327 |
- ), |
|
1328 |
- SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
1329 |
- signature=bytes.fromhex(""" |
|
1330 |
- 00 00 00 13 65 63 64 |
|
1331 |
- 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1332 |
- 00 00 00 8c |
|
1333 |
- 00 00 00 42 01 ce |
|
1334 |
- fe 9d 66 b6 01 76 2e 86 c2 ab 68 62 73 44 05 23 |
|
1335 |
- fd d1 79 07 fc 45 f5 c0 83 36 88 61 d4 04 79 90 |
|
1336 |
- b0 ef 8b 3c b5 55 0e cc 26 6b a0 3e 6a 04 48 ca |
|
1337 |
- e4 6a a5 a0 cf 91 5f 71 6f 37 9a 0f 6b a9 fb 9b |
|
1338 |
- 00 00 00 42 01 6d |
|
1339 |
- 21 77 c6 13 fa ea ac de 90 19 24 5a d2 61 39 d9 |
|
1340 |
- 66 9b 86 1a 41 04 58 a2 9b b8 93 b6 6f 82 23 f2 |
|
1341 |
- 01 23 c7 ff 5a d3 86 95 0f da 28 f9 3b e3 9c 27 |
|
1342 |
- e7 b2 d7 66 4e 5f 38 36 4c 8c be 76 4e fa 0a 2d |
|
1343 |
-"""), |
|
1344 |
- derived_passphrase=rb"""AAAAQgHO/p1mtgF2LobCq2hic0QFI/3ReQf8RfXAgzaIYdQEeZCw74s8tVUOzCZroD5qBEjK5GqloM+RX3FvN5oPa6n7mwAAAEIBbSF3xhP66qzekBkkWtJhOdlmm4YaQQRYopu4k7ZvgiPyASPH/1rThpUP2ij5O+OcJ+ey12ZOXzg2TIy+dk76Ci0=""", |
|
1345 |
- signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
1346 |
- ), |
|
1347 |
- }, |
|
1348 |
- ), |
|
1349 |
-} |
|
1350 |
-"""The master list of SSH test keys.""" |
|
1351 |
-SUPPORTED_KEYS: Mapping[str, SSHTestKey] = { |
|
1352 |
- k: v for k, v in ALL_KEYS.items() if v.is_suitable() |
|
1353 |
-} |
|
1354 |
-"""The subset of SSH test keys suitable for use with vault.""" |
|
1355 |
-UNSUITABLE_KEYS: Mapping[str, SSHTestKey] = { |
|
1356 |
- k: v for k, v in ALL_KEYS.items() if not v.is_suitable() |
|
1357 |
-} |
|
1358 |
-"""The subset of SSH test keys not suitable for use with vault.""" |
|
1359 |
- |
|
1360 |
-DUMMY_SERVICE = "service1" |
|
1361 |
-"""A standard/sample service name.""" |
|
1362 |
-DUMMY_PASSPHRASE = "my secret passphrase" |
|
1363 |
-"""A standard/sample passphrase.""" |
|
1364 |
-DUMMY_KEY1 = SUPPORTED_KEYS["ed25519"].public_key_data |
|
1365 |
-"""A sample universally supported SSH test key (in wire format).""" |
|
1366 |
-DUMMY_KEY1_B64 = base64.standard_b64encode(DUMMY_KEY1).decode("ASCII") |
|
1367 |
-""" |
|
1368 |
-A sample universally supported SSH test key (in `authorized_keys` format). |
|
1369 |
-""" |
|
1370 |
-DUMMY_KEY2 = SUPPORTED_KEYS["rsa"].public_key_data |
|
1371 |
-"""A second supported SSH test key (in wire format).""" |
|
1372 |
-DUMMY_KEY2_B64 = base64.standard_b64encode(DUMMY_KEY2).decode("ASCII") |
|
1373 |
-"""A second supported SSH test key (in `authorized_keys` format).""" |
|
1374 |
-DUMMY_KEY3 = SUPPORTED_KEYS["ed448"].public_key_data |
|
1375 |
-"""A third supported SSH test key (in wire format).""" |
|
1376 |
-DUMMY_KEY3_B64 = base64.standard_b64encode(DUMMY_KEY3).decode("ASCII") |
|
1377 |
-"""A third supported SSH test key (in `authorized_keys` format).""" |
|
1378 |
-DUMMY_CONFIG_SETTINGS = { |
|
1379 |
- "length": 10, |
|
1380 |
- "upper": 1, |
|
1381 |
- "lower": 1, |
|
1382 |
- "repeat": 5, |
|
1383 |
- "number": 1, |
|
1384 |
- "space": 1, |
|
1385 |
- "dash": 1, |
|
1386 |
- "symbol": 1, |
|
1387 |
-} |
|
1388 |
-"""Sample vault settings.""" |
|
1389 |
-DUMMY_RESULT_PASSPHRASE = b".2V_QJkd o" |
|
1390 |
-""" |
|
1391 |
-The passphrase derived from [`DUMMY_SERVICE`][] using [`DUMMY_PASSPHRASE`][]. |
|
1392 |
-""" |
|
1393 |
-DUMMY_RESULT_KEY1 = b"E<b<{ -7iG" |
|
1394 |
-""" |
|
1395 |
-The passphrase derived from [`DUMMY_SERVICE`][] using [`DUMMY_KEY1`][]. |
|
1396 |
-""" |
|
1397 |
-DUMMY_PHRASE_FROM_KEY1_RAW = ( |
|
1398 |
- b"\x00\x00\x00\x0bssh-ed25519" |
|
1399 |
- b"\x00\x00\x00@\xf0\x98\x19\x80l\x1a\x97\xd5&\x03n" |
|
1400 |
- b"\xcc\xe3e\x8f\x86f\x07\x13\x19\x13\t!33\xf9\xe46S" |
|
1401 |
- b"\x1d\xaf\xfd\r\x08\x1f\xec\xf8s\x9b\x8c_U9\x16|ST," |
|
1402 |
- b"\x1eR\xbb0\xed\x7f\x89\xe2/iQU\xd8\x9e\xa6\x02" |
|
1403 |
-) |
|
1404 |
-""" |
|
1405 |
-The "equivalent master passphrase" derived from [`DUMMY_KEY1`][] (raw format). |
|
1406 |
-""" |
|
1407 |
-DUMMY_PHRASE_FROM_KEY1 = b"8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==" |
|
1408 |
-""" |
|
1409 |
-The "equivalent master passphrase" derived from [`DUMMY_KEY1`][] (in base64). |
|
1410 |
-""" |
|
1411 |
- |
|
1412 |
-VAULT_MASTER_KEY = "vault key" |
|
1413 |
-""" |
|
1414 |
-The storage passphrase used to encrypt all sample vault native configurations. |
|
1415 |
-""" |
|
1416 |
-VAULT_V02_CONFIG = "P7xeh5y4jmjpJ2pFq4KUcTVoaE9ZOEkwWmpVTURSSWQxbGt6emN4aFE4eFM3anVPbDRNTGpOLzY3eDF5aE1YTm5LNWh5Q1BwWTMwM3M5S083MWRWRFlmOXNqSFJNcStGMWFOS3c2emhiOUNNenZYTmNNMnZxaUErdlRoOGF2ZHdGT1ZLNTNLOVJQcU9jWmJrR3g5N09VcVBRZ0ZnSFNUQy9HdFVWWnFteVhRVkY3MHNBdnF2ZWFEbFBseWRGelE1c3BFTnVUckRQdWJSL29wNjFxd2Y2ZVpob3VyVzRod3FKTElTenJ1WTZacTJFOFBtK3BnVzh0QWVxcWtyWFdXOXYyenNQeFNZbWt1MDU2Vm1kVGtISWIxWTBpcWRFbyswUVJudVVhZkVlNVpGWDA4WUQ2Q2JTWW81SnlhQ2Zxa3cxNmZoQjJES0Uyd29rNXpSck5iWVBrVmEwOXFya1NpMi9saU5LL3F0M3N3MjZKekNCem9ER2svWkZ0SUJLdmlHRno0VlQzQ3pqZTBWcTM3YmRiNmJjTkhqUHZoQ0NxMW1ldW1XOFVVK3pQMEtUMkRMVGNvNHFlOG40ck5KcGhsYXg1b1VzZ1NYU1B2T3RXdEkwYzg4NWE3YWUzOWI1MDI0MThhMWZjODQ3MDA2OTJmNDQ0MDkxNGFiNmRlMGQ2YjZiNjI5NGMwN2IwMmI4MGZi" |
|
1417 |
-""" |
|
1418 |
-A sample vault native configuration, in v0.2 format, encoded in base64 |
|
1419 |
-and encrypted with [`VAULT_MASTER_KEY`][]. |
|
1420 |
-""" |
|
1421 |
-VAULT_V02_CONFIG_DATA = { |
|
1422 |
- "global": { |
|
1423 |
- "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1424 |
- }, |
|
1425 |
- "services": { |
|
1426 |
- "(meta)": { |
|
1427 |
- "notes": "This config was originally in v0.2 format.", |
|
1428 |
- }, |
|
1429 |
- DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1430 |
- }, |
|
1431 |
-} |
|
1432 |
-""" |
|
1433 |
-The plaintext contents (a vault native configuration) stored in |
|
1434 |
-[`VAULT_V02_CONFIG`][]. |
|
1435 |
-""" |
|
1436 |
-VAULT_V03_CONFIG = "sBPBrr8BFHPxSJkV/A53zk9zwDQHFxLe6UIusCVvzFQre103pcj5xxmE11lMTA0U2QTYjkhRXKkH5WegSmYpAnzReuRsYZlWWp6N4kkubf+twZ9C3EeggPm7as2Af4TICHVbX4uXpIHeQJf9y1OtqrO+SRBrgPBzgItoxsIxebxVKgyvh1CZQOSkn7BIzt9xKhDng3ubS4hQ91fB0QCumlldTbUl8tj4Xs5JbvsSlUMxRlVzZ0OgAOrSsoWELXmsp6zXFa9K6wIuZa4wQuMLQFHiA64JO1CR3I+rviWCeMlbTOuJNx6vMB5zotKJqA2hIUpN467TQ9vI4g/QTo40m5LT2EQKbIdTvBQAzcV4lOcpr5Lqt4LHED5mKvm/4YfpuuT3I3XCdWfdG5SB7ciiB4Go+xQdddy3zZMiwm1fEwIB8XjFf2cxoJdccLQ2yxf+9diedBP04EsMHrvxKDhQ7/vHl7xF2MMFTDKl3WFd23vvcjpR1JgNAKYprG/e1p/7" |
|
1437 |
-""" |
|
1438 |
-A sample vault native configuration, in v0.3 format, encoded in base64 |
|
1439 |
-and encrypted with [`VAULT_MASTER_KEY`][]. |
|
1440 |
-""" |
|
1441 |
-VAULT_V03_CONFIG_DATA = { |
|
1442 |
- "global": { |
|
1443 |
- "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1444 |
- }, |
|
1445 |
- "services": { |
|
1446 |
- "(meta)": { |
|
1447 |
- "notes": "This config was originally in v0.3 format.", |
|
1448 |
- }, |
|
1449 |
- DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1450 |
- }, |
|
1451 |
-} |
|
1452 |
-""" |
|
1453 |
-The plaintext contents (a vault native configuration) stored in |
|
1454 |
-[`VAULT_V03_CONFIG`][]. |
|
1455 |
-""" |
|
1456 |
-VAULT_STOREROOM_CONFIG_ZIPPED = b""" |
|
1457 |
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-OWBXzJrpaZ4N7bscdwCF9XYesSMpxBDpwyCIVyJ8tHZVf/iS4pE6u+XgvD42yef+ujhM/AyboqPk |
|
1471 |
-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
-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
-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
-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
-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
-z8mWa0AAANB9f0ZvLZQhyDsnC0IMJShDBTuzJMZoktLn/ft79w/u7/dWvZb7OHz/Yf5+yYUBMTNK |
|
1477 |
-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
-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
-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
-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
-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
-O9bn2gutSFT6GO17ibguhXtItAjPbZWfyyQqHRyeBcpT7qbzQ6H1Of5clEqVdNcetAg8ZMKoWTbq |
|
1483 |
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-0/5YkO9er10YonsBT1rEn0lfBXDHwtwbxG2bdqELTuEtX2+OEih7K43rN2EvpXX47azaNpe/drIz |
|
1499 |
-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
-+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
-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
-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
-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
-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
-AgAAAgAAAAAAAAABAAAApIEEBgAAMDlQSwECHgMUAAAACACdVhlZyjtiYvgBAABrAgAAAgAAAAAA |
|
1506 |
-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 |
|
1507 |
-""" |
|
1508 |
-""" |
|
1509 |
-A sample vault native configuration, in storeroom format, encrypted with |
|
1510 |
-[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1511 |
-and then encoded in base64. |
|
1512 |
-""" |
|
1513 |
-VAULT_STOREROOM_CONFIG_DATA = { |
|
1514 |
- "global": { |
|
1515 |
- "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1516 |
- }, |
|
1517 |
- "services": { |
|
1518 |
- "(meta)": { |
|
1519 |
- "notes": "This config was originally in storeroom format.", |
|
1520 |
- }, |
|
1521 |
- DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1522 |
- }, |
|
1523 |
-} |
|
1524 |
-""" |
|
1525 |
-The parsed vault configuration stored in |
|
1526 |
-[`VAULT_STOREROOM_CONFIG_ZIPPED`][]. |
|
1527 |
-""" |
|
1528 |
- |
|
1529 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED_JAVASCRIPT_SOURCE = """ |
|
1530 |
-// Executed in the top-level directory of the vault project code, in Node.js. |
|
1531 |
-const storeroom = require('storeroom') |
|
1532 |
-const Store = require('./lib/store.js') |
|
1533 |
-let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1534 |
-await store._storeroom.put('/services/array/', ['entry1','entry2']) |
|
1535 |
-// The resulting "broken-dir" was then zipped manually. |
|
1536 |
-""" |
|
1537 |
-""" |
|
1538 |
-The JavaScript source for the script that generated the storeroom |
|
1539 |
-archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED`][]. |
|
1540 |
-""" |
|
1541 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED = b""" |
|
1542 |
-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
-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
-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
-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
-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
-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
-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
-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
-qp4ted4f91ZaR8+64C0BJBzDngElJEFLdA2WBcip2R/VZIG219WT3JlkbFrYSjhHWeb47igytTpo |
|
1551 |
-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
-vWwfWclm5uIB8cHbElx6G82Zs8RQnkDsyGVDbNaMOO7lMQF7o1Uy7Q9GuSWcFMK4KBAbcwm4l8RY |
|
1553 |
-+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
-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
-M1WVdH3+8x9QSwMEFAACAAgAeKMfWUXRU2i7AQAAFwIAAAIAAAAxYQ3QyZZjUAAA0H19Rm2zCGLs |
|
1556 |
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-RNpzxMMOPnCx3RRFgttiJTydfkB9MeZ9pvxP9jUm/fndQfJI83CsBxcEWhbjzlEparc3VS2s4LjR |
|
1569 |
-3Xafw3HLSlPqylHOWK2vc2ZJoObwqrCaFRg7kz1+z08SGu8pe0EHaII6FSxL7VM+rfVgpc1045Ut |
|
1570 |
-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
-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
-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
-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
-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
-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 |
|
1576 |
-""" |
|
1577 |
-""" |
|
1578 |
-A sample corrupted storeroom archive, encrypted with |
|
1579 |
-[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1580 |
-and then encoded in base64. |
|
1581 |
- |
|
1582 |
-The archive contains a directory `/services/array/` that claims to have |
|
1583 |
-two child items 'entry1' and 'entry2', but no such child items are |
|
1584 |
-present in the archive. See |
|
1585 |
-[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED_JAVASCRIPT_SOURCE`][] for |
|
1586 |
-the exact script that created this archive. |
|
1587 |
-""" |
|
1588 |
- |
|
1589 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2_JAVASCRIPT_SOURCE = """ |
|
1590 |
-// Executed in the top-level directory of the vault project code, in Node.js. |
|
1591 |
-const storeroom = require('storeroom') |
|
1592 |
-const Store = require('./lib/store.js') |
|
1593 |
-let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1594 |
-await store._storeroom.put('/services/array/', 'not a directory index') |
|
1595 |
-// The resulting "broken-dir" was then zipped manually. |
|
1596 |
-""" |
|
1597 |
-""" |
|
1598 |
-The JavaScript source for the script that generated the storeroom |
|
1599 |
-archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2`][]. |
|
1600 |
-""" |
|
1601 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2 = b""" |
|
1602 |
-UEsDBAoAAAAAAM6NSVmrcHdV5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV3ZS9LZkJp |
|
1603 |
-L0V0OUcrZmxYM3gxaFU4ZjE4YlE3S253bHoxN0IxSDE3cUhVOGdWK2RpWWY5MTdFZ0YrSStidEpZ |
|
1604 |
-VXBzWVZVck45OC9uLzdsZnl2NUdGVEg2NWZxVy93YjlOc2MxeEZ4ck43Q3p4eTZ5MVAxZzFPb2VK |
|
1605 |
-b0RZU3J6YXlwT0E2M3pidmk0ZTRiREMyNXhPTXl5NHBoMDFGeGdnQmpSNnpUcmR2UDk2UlZQd0I5 |
|
1606 |
-WitOZkZWZUlXT1NQN254ZFNYMGdFbkZ4SDBmWDkzNTFaTTZnPVBLAwQKAAAAAADOjUlZJg3/BhcC |
|
1607 |
-AAAXAgAAAgAAADBieyJ2ZXJzaW9uIjoxfQpBVXJJMjNDQ2VpcW14cUZRMlV4SUpBaUoxNEtyUzh2 |
|
1608 |
-SXpIa2xROURBaFRlVHNFMmxPVUg4WUhTcUk1cXRGSHBqY3c1WkRkZmRtUlEwQXVGRjllY3lkam14 |
|
1609 |
-dDdUemRYLzNmNFUvTGlVV2dLRmQ1K1FEN3BlVlE1bWpqeHNlUEpHTDlhTWlKaGxSUVB4SmtUbjBx |
|
1610 |
-U2poM1RUT0ZZbVAzV0JkdlUyWnF2RzhaSDk2cU1WcnZsQ0dMRmZTc2svVXlvcHZKdENONUVXcTRZ |
|
1611 |
-SDUwNFNiejFIUVhWd2RjejlrS1BuR3J6SVA4ZmZtZnhXQ0U0TmtLb0ZPQXZuNkZvS3FZdGlGbFE9 |
|
1612 |
-PQpBVXBMUVMrMG9VeEZTeCtxbTB3SUtyM1MvTVJxYWJJTFlEUnY0aHlBMVE2TGR2Nlk0UmJ0enVz |
|
1613 |
-NzRBc0cxbVhhenlRU2hlZVowdk0xM2ZyTFA4YlV0VHBaRyszNXF1eUhLM2NaWVJRZUxKM0JzejZz |
|
1614 |
-b0xaQjNZTkpNenFxTTQrdzM1U0FZZ2lMU1NkN05NeWVrTHNhRUIzRDFOajlTRk85K3NGNEpFMWVL |
|
1615 |
-UXpNMkltNk9qOUNVQjZUSTV3UitibksxN1BnY2RaeTZUMVRMWElVREVxcDg4dWdsWmRFTVcrNU9k |
|
1616 |
-aE5ZbXEzZERWVWV4UnJpM1AwUmVBSi9KMGdJNkNoUUE9PVBLAwQKAAAAAADOjUlZTNfdphcCAAAX |
|
1617 |
-AgAAAgAAADBmeyJ2ZXJzaW9uIjoxfQpBWVJqOVpIUktGUEVKOHM2YVY2TkRoTk5jQlZ5cGVYUmdz |
|
1618 |
-cnBldFQ0cGhJRGROWFdGYzRia0daYkJxMngwRDFkcVNjYWk5UzEveDZ2K28zRE0rVEF2OVE3ZFVR |
|
1619 |
-QWVKR3RmRkhJZDZxWW0ybEdNSnF5WTRNWm14aE9YdXliend0V3Q4Mnhvb041QTZNcWpINmxKQllD |
|
1620 |
-UUN3ZEJjb3RER0EwRnlnVTEzeHV2WnIzT1puZnFFRGRqbzMxNkw5aExDN1RxMTYwUHpBOXJOSDMz |
|
1621 |
-ZkNBcUhIVXZiYlFQQWErekw1d3dEN3FlWkY2MHdJaEwvRmk5L3JhNGJDcHZRNC9ORWpRd3c9PQpB |
|
1622 |
-WWNGUDB1Y2xMMHh3ZDM2UXZXbm4wWXFsOU5WV0s3c05CMTdjdmM3N3VDZ0J2OE9XYkR5UHk5d05h |
|
1623 |
-R2NQQzdzcVdZdHpZRlBHR0taVjhVUzA1YTVsV1BabDNGVFNuQXNtekxPelBlcFZxaitleDU3aEsx |
|
1624 |
-QnV1bHkrUCtYQkE0YUtsaDM3c0RJL3I0UE1BVlJuMDNoSDJ5dEhDMW9PbjF0V1M5Q1NLV1pSMThh |
|
1625 |
-djdTT0RBMVBNRnFYTmZKZVNTaVJiQ2htbDdOcFVLbjlXSGJZandybDlqN0JSdy9kWjhNQldCb3Ns |
|
1626 |
-Nlc1dGZtdnJMVHhGRFBXYUgzSUp0T0czMEI1M3c9PVBLAwQKAAAAAADOjUlZn9rNID8CAAA/AgAA |
|
1627 |
-AgAAADFkeyJ2ZXJzaW9uIjoxfQpBYWFBb3lqaGljVDZ4eXh1c0U0RVlDZCtxbE81Z0dEYTBNSFVS |
|
1628 |
-MmgrSW9QMHV4UkY3b1BRS2czOHlQUEN3Ny9MYVJLQ0dQZ0RyZ2RpTWJTeUwzZ3ZNMFhseVpVMVBW |
|
1629 |
-QVJvNEFETU9lbXgrOWhtS0hjQWNKMG5EeW5oSkhGYTYyb2xyQUNxekZzblhKNVBSeEVTVzVEbUh0 |
|
1630 |
-Ui9nRm5Wa1FvalhyVW4ybmpYMjVVanZQaXhlMU96Y0daMmQ0MjdVTGdnY1hqMkhSdjJiZldDNDUw |
|
1631 |
-SGFXS3FDckZlYWlrQ2xkUUM2WGV3SkxZUjdvQUY3UjVha2ttK3M2MXNCRTVCaTg0QmJLWHluc1NG |
|
1632 |
-ejE0TXFrd2JMK1VMYVk9CkFUT3dqTUFpa3Q4My9NTW5KRXQ2b3EyNFN4KzJKNDc2K2gyTmEzbHUr |
|
1633 |
-MDg0cjlBT25aaUk0TmlYV0N1Q0lzakEzcTBwUHFJS1VXZHlPQW9uM2VHY0huZUppWUtVYllBaUJI |
|
1634 |
-MVNmbnhQQkMzZkFMRklybkQ4Y0VqeGpPcUFUaTQ5dE1mRmtib0dNQ3dEdFY0V3NJL0tLUlRCOFd1 |
|
1635 |
-MnNXK2J0V3QzVWlvZG9ZeUVLTDk3ekNNemZqdGptejF4SDhHTXY5WDVnaG9NSW5RQVNvYlRreVZ4 |
|
1636 |
-dWo5YnlDazdNbU0vK21ZL3AwZE9oYVY0Nncwcm04UGlvWEtzdzR4bXB3ditDWC9PRXV3Uy9meDJT |
|
1637 |
-Y0lOQnNuYVRiWT1QSwECHgMKAAAAAADOjUlZq3B3VeYAAADmAAAABQAAAAAAAAAAAAAApIEAAAAA |
|
1638 |
-LmtleXNQSwECHgMKAAAAAADOjUlZJg3/BhcCAAAXAgAAAgAAAAAAAAAAAAAApIEJAQAAMGJQSwEC |
|
1639 |
-HgMKAAAAAADOjUlZTNfdphcCAAAXAgAAAgAAAAAAAAAAAAAApIFAAwAAMGZQSwECHgMKAAAAAADO |
|
1640 |
-jUlZn9rNID8CAAA/AgAAAgAAAAAAAAAAAAAApIF3BQAAMWRQSwUGAAAAAAQABADDAAAA1gcAAAAA |
|
1641 |
-""" |
|
1642 |
-""" |
|
1643 |
-A sample corrupted storeroom archive, encrypted with |
|
1644 |
-[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1645 |
-and then encoded in base64. |
|
1646 |
- |
|
1647 |
-The archive contains a directory `/services/array/` whose list of child |
|
1648 |
-items does not adhere to the serialization format. See |
|
1649 |
-[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2_JAVASCRIPT_SOURCE`][] for |
|
1650 |
-the exact script that created this archive. |
|
1651 |
-""" |
|
1652 |
- |
|
1653 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3_JAVASCRIPT_SOURCE = """ |
|
1654 |
-// Executed in the top-level directory of the vault project code, in Node.js. |
|
1655 |
-const storeroom = require('storeroom') |
|
1656 |
-const Store = require('./lib/store.js') |
|
1657 |
-let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1658 |
-await store._storeroom.put('/services/array/', [null, 1, true, [], {}]) |
|
1659 |
-// The resulting "broken-dir" was then zipped manually. |
|
1660 |
-""" |
|
1661 |
-""" |
|
1662 |
-The JavaScript source for the script that generated the storeroom |
|
1663 |
-archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3`][]. |
|
1664 |
-""" |
|
1665 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3 = b""" |
|
1666 |
-UEsDBAoAAAAAAEOPSVnVlcff5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV4dVBHUDBi |
|
1667 |
-YkxrUVdvWnV5ZUJQRy8xdmM2MCt6MThOa3BsS09ydFAvUTVnQmxkYVpIOG10dTE5VWZFNGdGRGRj |
|
1668 |
-eHJtWUd4eXZDZFNqcVlOaDh4cTlzM3VydkdRTWFwcnhtdlZGZUxoSW4zZnVlTDAweEk0ZmlLenZN |
|
1669 |
-MmthUlRsNWNORGh3eUNlWVk4dzhBcXNhYjNyVWVsOEE0eVQ0cHU2d2tmQ3dTWUdqeG5HR29EcWJK |
|
1670 |
-VnVJVWNpZVBEcU9PTzU2b0MyMG9lT01adFVkTUtxV28zYnFZPVBLAwQKAAAAAABDj0lZ77OVHxcC |
|
1671 |
-AAAXAgAAAgAAADBjeyJ2ZXJzaW9uIjoxfQpBZllFQVVobEkyU2lZeGlrdWh0RzRNbUN3L1V2THBN |
|
1672 |
-VVhwVlB0NlRwdzRyNGdocVJhbGZWZ0hxUHFtbTczSnltdFFrNnZnR2JRdUpiQmVlYjYwOHNrMGk4 |
|
1673 |
-ZFJVZjNwdlc2SnUyejljQkdwOG5mTFpTdlNad1lLN09UK2gzSDNDcmoxbXNicEZUcHVldW81NXc1 |
|
1674 |
-dGdYMnBuWXNWTVcrczdjaHEyMUIya2lIVEZrdGt1MXlaRzhPYkVUQjNCOFNGODVVbi9CUjFEMHJ1 |
|
1675 |
-ME9zOWl4ZWM2VmNTMitTZndtNnNtSlk2ZW9ZNTJzOGJNRGdYMndjQ0srREdkOEo2VWp0NG5OQVE9 |
|
1676 |
-PQpBUWlPRnRZcmJybWUycEwxRFpGT1BjU0RHOUN2cVkvbHhTWGIwaVJUdmtIWFc2bEtHL0p4RUtU |
|
1677 |
-d3RTc0RTeDhsMTUvaHRmbWpOQ2tuTzhLVEFoKzhRQm5FbjZ0a2x5Y3BmeEIrTUxLRjFCM1Q1bjcv |
|
1678 |
-T0VUMExMdmgxU2k1bnRRNXhTUHZZNWtXeUMyZjhXUXFZb3FSNU5JVENMeDV6dWNsQ3dGb2kvVXc4 |
|
1679 |
-OWNNWjM1MHBSbThzUktJbjJFeDUrQ1JwS3ZHdnBHbFJaTmk5VHZmVkNic1FCalR3MC9aeklTdzVQ |
|
1680 |
-NW9BVWE2U1ExUVFnNHg4VUNkY0s2QUNLaFluY0d4TVE9PVBLAwQKAAAAAABDj0lZGk9LVj8CAAA/ |
|
1681 |
-AgAAAgAAADE0eyJ2ZXJzaW9uIjoxfQpBY1g2NVpMUWk4ck9pUlIyWGEwQlFHQVhQVWF2aHNJVGVY |
|
1682 |
-c2dzRk9OUmFTRzJCQlg0SGxJRHpwRUd5aDUrZ2czZVRwWDFNOERua3pMeTVzcWRkMFpmK3padTgz |
|
1683 |
-Qm52Y1JPREVIVDllUW91YUtPTWltdlRYanNuSXAxUHo5VGY1TlRkRjNJVTd2V1lhUDg4WTI5NG1i |
|
1684 |
-c1VVL2RKVTZqZ3ZDbUw2cE1VZ28xUU12bGJnaVp3cDV1RDFQZXlrSXdKVWdJSEgxTEpnYi9xU2tW |
|
1685 |
-c25leW1XY1RXR0NobzRvZGx3S2hJWmFCelhvNFhlN2U1V2I2VHA3Rkk5VUpVcmZIRTAvcVdrZUZE |
|
1686 |
-VmxlazY3cUx3ZFZXcU9DdFk9CkFhSGR0QjhydmQ0U3N4ZmJ5eU1OOHIzZEoxeHA5NmFIRTQvalNi |
|
1687 |
-Z05hZWttaDkyb2ROM1F4MUlqYXZsYVkxeEt1eFF3KzlwTHFIcTF5a1JSRjQzL2RVWGFIRk5UU0NX |
|
1688 |
-OVFsdmd3KzMwa1ZhSEdXRllvbFRnRWE4djQ3b3VrbGlmc01PZGM0YVNKb2R4ZUFJcVc3Q1cwdDVR |
|
1689 |
-b2RUbWREUXpqc3phZkQ4R2VOd2NFQjdGMHI2RzNoZEJlQndxd3Z6eENVYnpSUmU5bEQ3NjQ3RFp1 |
|
1690 |
-bEo1U3c4amlvV0paTW40NlZhV3BYUXk4UnNva3hHaW00WUpybUZIQ2JkVU9qSWJsUmQ1Z3VhUDNU |
|
1691 |
-M0NxeHRPdC94b1BhOD1QSwMECgAAAAAAQ49JWVJM8QYXAgAAFwIAAAIAAAAxNnsidmVyc2lvbiI6 |
|
1692 |
-MX0KQVlCWDF6M21qUlQrand4M2FyNkFpemxnalJZbUM0ZHg5NkxVQVBTVHNMWXJKVHFtWnd5N0Jy |
|
1693 |
-OFlCcElVamorMHdlT3lNaUtLVnFwaER3RXExNWFqUmlSZUVEQURTVHZwWmlLZUlnZjR5elUzZXNP |
|
1694 |
-eDJ2U2J1bXhTK0swUGZVa2tsSy9TRmRiU3EvUHFMRjBDRTVCMXNyKzJLYTB2WlJmak94R3VFeFRD |
|
1695 |
-RXozN0ZlWDNNR3NCNkhZVHEzaUJWcUR6NVB6eHpCWWM5Kyt6RitLS1RnMVp2NGRtRmVQTC9JSEY5 |
|
1696 |
-WnV6TWlqRXdCRkE3WnJ0dkRqd3ZYcWtsMVpsR0c4eUV3PT0KQVhUWkRLVnNleldpR1RMUVZqa2hX |
|
1697 |
-bXBnK05MYlM0M2MxZEpvK2xGcC9yWUJYZkw3Wll5cGdjWE5IWXNzd01nc2VSSTAzNmt6bGZkdGNa |
|
1698 |
-bTdiUUN6M2JuQmZ6ZlorZFFuT2Y5STVSU2l0QzB2UmsydkQrOFdwbmRPSzNucGY5S0VpWklOSzVq |
|
1699 |
-TEZGTTJDTkNmQzBabXNRUlF3T0k2N3l5ZHhjVnFDMXBnWHV6QXRXamlsSUpnN0p6eUtsY3BJUGJu |
|
1700 |
-SUc0UzRSUlhIdW1wZnpoeWFZWkd6T0FDamRSYTZIMWJxYkJkZXFaSHMvQXJvM25mVjdlbjhxSUE5 |
|
1701 |
-aVUrbnNweXFnPT1QSwECHgMKAAAAAABDj0lZ1ZXH3+YAAADmAAAABQAAAAAAAAAAAAAApIEAAAAA |
|
1702 |
-LmtleXNQSwECHgMKAAAAAABDj0lZ77OVHxcCAAAXAgAAAgAAAAAAAAAAAAAApIEJAQAAMGNQSwEC |
|
1703 |
-HgMKAAAAAABDj0lZGk9LVj8CAAA/AgAAAgAAAAAAAAAAAAAApIFAAwAAMTRQSwECHgMKAAAAAABD |
|
1704 |
-j0lZUkzxBhcCAAAXAgAAAgAAAAAAAAAAAAAApIGfBQAAMTZQSwUGAAAAAAQABADDAAAA1gcAAAAA |
|
1705 |
-""" |
|
1706 |
-""" |
|
1707 |
-A sample corrupted storeroom archive, encrypted with |
|
1708 |
-[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1709 |
-and then encoded in base64. |
|
1710 |
- |
|
1711 |
-The archive contains a directory `/services/array/` whose list of child |
|
1712 |
-items are not all valid item names. See |
|
1713 |
-[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3_JAVASCRIPT_SOURCE`][] for |
|
1714 |
-the exact script that created this archive. |
|
1715 |
-""" |
|
1716 |
- |
|
1717 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4_JAVASCRIPT_SOURCE = """ |
|
1718 |
-// Executed in the top-level directory of the vault project code, in Node.js. |
|
1719 |
-const storeroom = require('storeroom') |
|
1720 |
-const Store = require('./lib/store.js') |
|
1721 |
-let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1722 |
-await store._storeroom.put('/dir/subdir/', []) |
|
1723 |
-await store._storeroom.put('/dir/', []) |
|
1724 |
-// The resulting "broken-dir" was then zipped manually. |
|
1725 |
-""" |
|
1726 |
-""" |
|
1727 |
-The JavaScript source for the script that generated the storeroom |
|
1728 |
-archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4`][]. |
|
1729 |
-""" |
|
1730 |
-VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4 = b""" |
|
1731 |
-UEsDBAoAAAAAAE+5SVloORS+5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV6dWRoNkRQ |
|
1732 |
-YTlNSWFabHZ5TytVYTFuamhjV2hIaTFBU0lKYW5zcXBxVlA0blN2V0twUzdZOUc2bjFSbi8vUnVM |
|
1733 |
-VitwcHp5SC9RQk83R0hFenNVMzdCUzFwUmVVeGhxUVlVTE56OXZvQ0crM1ZaL3VncU44dDJiU05m |
|
1734 |
-Nyt5K3hiNng2aVlFUmNZYTJ0UkhzZVdIc0laTE9ha2lDb0lRVGV3cndwYjVMM2pnd0E3SXBzaDkz |
|
1735 |
-QkxHSzM5dXNYNmo0R0I2WkRUeW5JcGk4V3JkbDhnWVZCN0tVPVBLAwQKAAAAAABPuUlZ663uUhcC |
|
1736 |
-AAAXAgAAAgAAADAzeyJ2ZXJzaW9uIjoxfQpBV2wzS2gzd21ZSFVZZU1RR3BLSVowdVd1VXFna09h |
|
1737 |
-YmRjNzNYYXVsZTNtVS9sN2Zvd1AyS21jbFp3ZDM5V3lYVzRTcEw4R0l4YStDZW51S3V0Wm5nb0FR |
|
1738 |
-bWlnaUJUbkFaais5TENCcGNIWlZNY2RBVkgxKzBFNGpsanZ1UkVwZ0tPS05LZjRsTUl1QnZ4VmFB |
|
1739 |
-ZkdwNHJYNEZ4MmpPSlk1Y3NQZzBBRFBoZVAwN29GWVQ3alorSUNEK1AxNGZPdWpwMGRUeDRrTDIy |
|
1740 |
-LzlqalRDNXBCNVF5NW5iOUx3Zk5DUWViSUVpaTZpbU0vRmFrK1dtV05tMndqMERSTEc4RHY3ZkE9 |
|
1741 |
-PQpBU0c3NTNGTVVwWmxjK3E1YXRzcC93OUNqN2JPOFlpY24wZHg2UGloTmwzUS9WSjVVeGJmU3l0 |
|
1742 |
-ZDFDNDBRU2xXeTJqOTJDWUd3VER6eEdBMXVnb0FCYi9kTllTelVwbHJFb3BuUVphYXdsdTVwV2x0 |
|
1743 |
-Y1E5WTcveWN4S2E4b0JaaGY3RkFYcGo2c01wUW9zNzI5VFVabFd4UmI4VFRtN2FrVnR1OXcvYXlK |
|
1744 |
-RS9reDh4ZUYxSGJlc3Q4N1IxTGg2ODd3dS9XVUN2ZjNXYXo1VjNnZWY0RnpUTXg0bkpqSlZOd0U0 |
|
1745 |
-SzAxUTlaVzQ0bmVvbExPUVI1MkZDeDZvbml3RW9tenc9PVBLAwQKAAAAAABPuUlZRXky4CsCAAAr |
|
1746 |
-AgAAAgAAADEweyJ2ZXJzaW9uIjoxfQpBWmlYWVlvNUdCY2d5dkFRaGtyK2ZjUkdVSkdabDd2dE5w |
|
1747 |
-T2Mrd1VzbXJhQWhRN3dKdlYraGhKcTlrcWNKQnBWU0gyUTBTTVVhb29iNjBJM1NYNUNtTkJRU2FH |
|
1748 |
-M3prd0Y0T2F4TnpCZUh0NFlpaDd4Y3p2ak4xR0hISDJQYW0xam05K09ja3JLVmNMVURtNXRKb2ZC |
|
1749 |
-Z1E4Q2NwMGZMVkdEaURjNWF0MjVMc2piQVcvNkZFSnJ5VVBHWis4UVdYRmlWMGdtVVZybVc3VUFy |
|
1750 |
-dGhJQitWNTdZS1BORi95Nng2OU43UTFQbmp1cUczdlpybzljMEJ3d012NWoyc3BMMTJHcTdzTDZE |
|
1751 |
-alB1d0dHbnB2MkVZQTFLbmc9CkFTdjQwUkgzRmxzbGVlU1NjRlZNRmh3dEx6eEYxK2xpcmxEL29X |
|
1752 |
-alJLQ05qVWZhUVpJTWpqMWRoVkhOakNUTWhWZ1ZONkl3b04xTnFOMEV6cmdhaTFBWnNiMm9UczYw |
|
1753 |
-QkI1UGh0U0hhQ2U2WllUeE1JemFPS2FIK0w2eHhtaXIrTlQxNTRXS0x5amJMams3MU1na3Nwa0Yy |
|
1754 |
-WDBJMnlaWW5IUUM0bmdEL24yZzRtSVI2Q1hWL0JOUXNzeTBEeXdGLzN6eGRRYWw5cFBtVk1qYnFu |
|
1755 |
-cHY5SFNqRTg4S25naVpBWFhJWU1OVGF2L3Q3Y3dEWGdNekhKTlU0Y2xnVUtIQVZ3QT09UEsDBAoA |
|
1756 |
-AAAAAE+5SVkPfKx9FwIAABcCAAACAAAAMWR7InZlcnNpb24iOjF9CkFYbHNLRzQwZG5ibTJvcXdY |
|
1757 |
-U2ZrSWp3Mmxpa0lDS3hVOXU3TU52VkZ1NEJ2R1FVVitSVVdsS3MxL25TSlBtM2U2OTRvVHdoeDFo |
|
1758 |
-RFF3U0M5U0QvbXd5bnpjSTloUnRCUWVXMkVMOVU5L1ZGcHFsVWY3Z1ZOMHZ0ZWpXYnV4QnhsZlRD |
|
1759 |
-Tys4SFBwU2Zaa2VOUld5R2JNdzBFSU9LTmxRYjk3OUF0c1g3THR0NytaTkJnakZHYkZxaHdwa3kx |
|
1760 |
-WUNDVng1UmNZZ2tma2ZjWnVncGpzc1RzNVFvK1p3QXBEcDZ4V3JjSHMxUDhvNktBRzAwcjZZbkNM |
|
1761 |
-N2ErU1dwZmVNTUJhZz09CkFadVF0cFZMWmVvb292NkdyQlpnb3B6VmRGUXBlK1h6QXZuZ2dPVnZM |
|
1762 |
-VWtCYVF2akl5K1VLdXVUVlFoQ1JiMVp6dGZQL2dsNnoxOEsyZW5sQlo2bGJTZnoxTlBWeUVzYXB3 |
|
1763 |
-dDVpUVh4azd5UkJlZks1cFlsNTduUXlmcFZQbzlreFpnOVdHTkV3NVJ5MkExemhnNGl6TWxLRmJh |
|
1764 |
-UjZFZ0FjQ3NFOXAveGRLa29ZNjhOUlZmNXJDM3lMQjc3ZWgyS1hCUld2WDNZcE9XdW00OGtsbmtI |
|
1765 |
-akJjMFpiQmUrT3NZb3d5cXpoRFA2ZGQxRlFnMlFjK09vc3B4V0sycld4M01HZz09UEsBAh4DCgAA |
|
1766 |
-AAAAT7lJWWg5FL7mAAAA5gAAAAUAAAAAAAAAAAAAAKSBAAAAAC5rZXlzUEsBAh4DCgAAAAAAT7lJ |
|
1767 |
-Weut7lIXAgAAFwIAAAIAAAAAAAAAAAAAAKSBCQEAADAzUEsBAh4DCgAAAAAAT7lJWUV5MuArAgAA |
|
1768 |
-KwIAAAIAAAAAAAAAAAAAAKSBQAMAADEwUEsBAh4DCgAAAAAAT7lJWQ98rH0XAgAAFwIAAAIAAAAA |
|
1769 |
-AAAAAAAAAKSBiwUAADFkUEsFBgAAAAAEAAQAwwAAAMIHAAAAAA== |
|
1770 |
-""" |
|
1771 |
-""" |
|
1772 |
-A sample corrupted storeroom archive, encrypted with |
|
1773 |
-[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1774 |
-and then encoded in base64. |
|
1775 |
- |
|
1776 |
-The archive contains two directories `/dir/` and `/dir/subdir/`, where |
|
1777 |
-`/dir/subdir/` is a correctly serialized directory, but `/dir/` does not |
|
1778 |
-contain `/dir/subdir/` in its list of child items. See |
|
1779 |
-[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4_JAVASCRIPT_SOURCE`][] for |
|
1780 |
-the exact script that created this archive. |
|
1781 |
-""" |
|
1782 |
- |
|
1783 |
-CANNOT_LOAD_CRYPTOGRAPHY = ( |
|
1784 |
- "Cannot load the required Python module 'cryptography'." |
|
1785 |
-) |
|
1786 |
-""" |
|
1787 |
-The expected `derivepassphrase` error message when the `cryptography` |
|
1788 |
-module cannot be loaded, which is needed e.g. by the `export vault` |
|
1789 |
-subcommands. |
|
1790 |
-""" |
|
1791 |
- |
|
1792 |
-skip_if_cryptography_support = pytest.mark.skipif( |
|
1793 |
- importlib.util.find_spec("cryptography") is not None, |
|
1794 |
- reason='cryptography support available; cannot test "no support" scenario', |
|
1795 |
-) |
|
1796 |
-""" |
|
1797 |
-A cached pytest mark to skip this test if cryptography support is |
|
1798 |
-available. Usually this means that the test targets |
|
1799 |
-`derivepassphrase`'s fallback functionality, which is not available |
|
1800 |
-whenever the primary functionality is. |
|
1801 |
-""" |
|
1802 |
-skip_if_no_cryptography_support = pytest.mark.skipif( |
|
1803 |
- importlib.util.find_spec("cryptography") is None, |
|
1804 |
- reason='no "cryptography" support', |
|
1805 |
-) |
|
1806 |
-""" |
|
1807 |
-A cached pytest mark to skip this test if cryptography support is not |
|
1808 |
-available. Usually this means that the test targets the |
|
1809 |
-`derivepassphrase export vault` subcommand, whose functionality depends |
|
1810 |
-on cryptography support being available. |
|
1811 |
-""" |
|
1812 |
-skip_if_on_the_annoying_os = pytest.mark.skipif( |
|
1813 |
- sys.platform == "win32", |
|
1814 |
- reason="The Annoying OS behaves differently.", |
|
1815 |
-) |
|
1816 |
-""" |
|
1817 |
-A cached pytest mark to skip this test if running on The Annoying |
|
1818 |
-Operating System, a.k.a. Microsoft Windows. Usually this is due to |
|
1819 |
-unnecessary and stupid differences in the OS internals, and these |
|
1820 |
-differences are deemed irreconcilable in the context of the decorated |
|
1821 |
-test, so the test is to be skipped. |
|
1822 |
- |
|
1823 |
-See also: |
|
1824 |
- [`xfail_on_the_annoying_os`][] |
|
1825 |
- |
|
1826 |
-""" |
|
1827 |
-skip_if_no_multiprocessing_support = pytest.mark.skipif( |
|
1828 |
- importlib.util.find_spec("multiprocessing") is None, |
|
1829 |
- reason='no "multiprocessing" support', |
|
1830 |
-) |
|
1831 |
-""" |
|
1832 |
-A cached pytest mark to skip this test if multiprocessing support is not |
|
1833 |
-available. Usually this means that the test targets the concurrency |
|
1834 |
-features of `derivepassphrase`, which is generally only possible to test |
|
1835 |
-in separate processes because the testing machinery operates on |
|
1836 |
-process-global state. |
|
1837 |
-""" |
|
1838 |
- |
|
1839 |
-MIN_CONCURRENCY = 4 |
|
1840 |
-""" |
|
1841 |
-The minimum amount of concurrent threads used for testing. |
|
1842 |
-""" |
|
1843 |
- |
|
1844 |
- |
|
1845 |
-def get_concurrency_limit() -> int: |
|
1846 |
- """Return the imposed limit on the number of concurrent threads. |
|
1847 |
- |
|
1848 |
- We use [`os.process_cpu_count`][] as the limit on Python 3.13 and |
|
1849 |
- higher, and [`os.cpu_count`][] on Python 3.12 and below. On |
|
1850 |
- Python 3.12 and below, we explicitly support the `PYTHON_CPU_COUNT` |
|
1851 |
- environment variable. We guarantee at least [`MIN_CONCURRENCY`][] |
|
1852 |
- many threads in any case. |
|
1853 |
- |
|
1854 |
- """ # noqa: RUF002 |
|
1855 |
- result: int | None = None |
|
1856 |
- if sys.version_info >= (3, 13): |
|
1857 |
- result = os.process_cpu_count() |
|
1858 |
- else: |
|
1859 |
- with contextlib.suppress(KeyError, ValueError): |
|
1860 |
- result = result or int(os.environ["PYTHON_CPU_COUNT"], 10) |
|
1861 |
- with contextlib.suppress(AttributeError): |
|
1862 |
- result = result or len(os.sched_getaffinity(os.getpid())) |
|
1863 |
- return max(result if result is not None else 0, MIN_CONCURRENCY) |
|
1864 |
- |
|
1865 |
- |
|
1866 |
-def get_concurrency_step_count( |
|
1867 |
- settings: hypothesis.settings | None = None, |
|
1868 |
-) -> int: |
|
1869 |
- """Return the desired step count for concurrency-related tests. |
|
1870 |
- |
|
1871 |
- This is the smaller of the [general concurrency |
|
1872 |
- limit][tests.get_concurrency_limit] and the step count from the |
|
1873 |
- current hypothesis settings. |
|
1874 |
- |
|
1875 |
- Args: |
|
1876 |
- settings: |
|
1877 |
- The hypothesis settings for a specific tests. If not given, |
|
1878 |
- then the current profile will be queried directly. |
|
1879 |
- |
|
1880 |
- """ |
|
1881 |
- if settings is None: # pragma: no cover |
|
1882 |
- settings = hypothesis.settings() |
|
1883 |
- return min(get_concurrency_limit(), settings.stateful_step_count) |
|
1884 |
- |
|
1885 |
- |
|
1886 |
-def xfail_on_the_annoying_os( |
|
1887 |
- f: Callable | None = None, |
|
1888 |
- /, |
|
1889 |
- *, |
|
1890 |
- reason: str = "", |
|
1891 |
-) -> pytest.MarkDecorator | Any: # pragma: no cover |
|
1892 |
- """Annotate a test which fails on The Annoying OS. |
|
1893 |
- |
|
1894 |
- Annotate a test to indicate that it fails on The Annoying Operating |
|
1895 |
- System, a.k.a. Microsoft Windows. Usually this is due to |
|
1896 |
- differences in the design of OS internals, and usually, these |
|
1897 |
- differences are both unnecessary and stupid. |
|
1898 |
- |
|
1899 |
- Args: |
|
1900 |
- f: |
|
1901 |
- A callable to decorate. If not given, return the pytest |
|
1902 |
- mark directly. |
|
1903 |
- reason: |
|
1904 |
- An optional, more detailed reason stating why this test |
|
1905 |
- fails on The Annoying OS. |
|
1906 |
- |
|
1907 |
- Returns: |
|
1908 |
- The callable, marked as an expected failure on the Annoying OS, |
|
1909 |
- or alternatively a suitable pytest mark if no callable was |
|
1910 |
- passed. The reason will begin with the phrase "The Annoying OS |
|
1911 |
- behaves differently.", and the optional detailed reason, if not |
|
1912 |
- empty, will follow. |
|
1913 |
- |
|
1914 |
- """ |
|
1915 |
- base_reason = "The Annoying OS behaves differently." |
|
1916 |
- full_reason = base_reason if not reason else f"{base_reason} {reason}" |
|
1917 |
- mark = pytest.mark.xfail( |
|
1918 |
- sys.platform == "win32", |
|
1919 |
- reason=full_reason, |
|
1920 |
- raises=(AssertionError, hypothesis.errors.FailedHealthCheck), |
|
1921 |
- strict=True, |
|
1922 |
- ) |
|
1923 |
- return mark if f is None else mark(f) |
|
1924 |
- |
|
1925 |
- |
|
1926 |
-@socketprovider.SocketProvider.register("stub_agent") |
|
1927 |
-class StubbedSSHAgentSocket: |
|
1928 |
- """A stubbed SSH agent presenting an [`_types.SSHAgentSocket`][].""" |
|
1929 |
- |
|
1930 |
- _SOCKET_IS_CLOSED = "Socket is closed." |
|
1931 |
- _NO_FLAG_SUPPORT = "This stubbed SSH agent socket does not support flags." |
|
1932 |
- _PROTOCOL_VIOLATION = "SSH agent protocol violation." |
|
1933 |
- _INVALID_REQUEST = "Invalid request." |
|
1934 |
- _UNSUPPORTED_REQUEST = "Unsupported request." |
|
1935 |
- |
|
1936 |
- HEADER_SIZE = 4 |
|
1937 |
- CODE_SIZE = 1 |
|
1938 |
- |
|
1939 |
- KNOWN_EXTENSIONS = frozenset({ |
|
1940 |
- "query", |
|
1941 |
- "list-extended@putty.projects.tartarus.org", |
|
1942 |
- }) |
|
1943 |
- """Known and implemented protocol extensions.""" |
|
1944 |
- |
|
1945 |
- def __init__(self, *extensions: str) -> None: |
|
1946 |
- """Initialize the agent.""" |
|
1947 |
- self.send_to_client = bytearray() |
|
1948 |
- """ |
|
1949 |
- The buffered response to the client, read piecemeal by [`recv`][]. |
|
1950 |
- """ |
|
1951 |
- self.receive_from_client = bytearray() |
|
1952 |
- """The last request issued by the client.""" |
|
1953 |
- self.closed = False |
|
1954 |
- """True if the connection is closed, false otherwise.""" |
|
1955 |
- self.enabled_extensions = frozenset(extensions) & self.KNOWN_EXTENSIONS |
|
1956 |
- """ |
|
1957 |
- Extensions actually enabled in this particular stubbed SSH agent. |
|
1958 |
- """ |
|
1959 |
- self.try_rfc6979 = False |
|
1960 |
- """ |
|
1961 |
- Attempt to issue DSA and ECDSA signatures according to RFC 6979? |
|
1962 |
- """ |
|
1963 |
- self.try_pageant_068_080 = False |
|
1964 |
- """ |
|
1965 |
- Attempt to issue DSA and ECDSA signatures as per Pageant 0.68–0.80? |
|
1966 |
- """ # noqa: RUF001 |
|
1967 |
- |
|
1968 |
- def __enter__(self) -> Self: |
|
1969 |
- """Return self.""" |
|
1970 |
- return self |
|
1971 |
- |
|
1972 |
- def __exit__(self, *args: object) -> None: |
|
1973 |
- """Mark the agent's socket as closed.""" |
|
1974 |
- self.closed = True |
|
1975 |
- |
|
1976 |
- def sendall(self, data: Buffer, flags: int = 0, /) -> None: |
|
1977 |
- """Send data to the SSH agent. |
|
1978 |
- |
|
1979 |
- The signature, and behavior, is identical to |
|
1980 |
- [`socket.socket.sendall`][]. Upon successful sending, this |
|
1981 |
- agent will parse the request, call the appropriate handler, and |
|
1982 |
- buffer the result such that it can be read via [`recv`][], in |
|
1983 |
- accordance with the SSH agent protocol. |
|
1984 |
- |
|
1985 |
- Args: |
|
1986 |
- data: Binary data to send to the agent. |
|
1987 |
- flags: Reserved. Must be 0. |
|
1988 |
- |
|
1989 |
- Returns: |
|
1990 |
- Nothing. The result should be requested via [`recv`][], and |
|
1991 |
- interpreted in accordance with the SSH agent protocol. |
|
1992 |
- |
|
1993 |
- Raises: |
|
1994 |
- AssertionError: |
|
1995 |
- The flags argument, if specified, must be 0. |
|
1996 |
- ValueError: |
|
1997 |
- The agent's socket is already closed. No further |
|
1998 |
- requests can be sent. |
|
1999 |
- |
|
2000 |
- """ |
|
2001 |
- assert not flags, self._NO_FLAG_SUPPORT |
|
2002 |
- if self.closed: |
|
2003 |
- raise ValueError(self._SOCKET_IS_CLOSED) |
|
2004 |
- self.receive_from_client.extend(memoryview(data)) |
|
2005 |
- try: |
|
2006 |
- self.parse_client_request_and_dispatch() |
|
2007 |
- except ValueError: |
|
2008 |
- payload = int.to_bytes(_types.SSH_AGENT.FAILURE.value, 1, "big") |
|
2009 |
- self.send_to_client.extend(int.to_bytes(len(payload), 4, "big")) |
|
2010 |
- self.send_to_client.extend(payload) |
|
2011 |
- finally: |
|
2012 |
- self.receive_from_client.clear() |
|
2013 |
- |
|
2014 |
- def recv(self, count: int, flags: int = 0, /) -> bytes: |
|
2015 |
- """Read data from the SSH agent. |
|
2016 |
- |
|
2017 |
- As per the SSH agent protocol, data is only available to be read |
|
2018 |
- immediately after a request via [`sendall`][]. Calls to |
|
2019 |
- [`recv`][] at other points in time that attempt to read data |
|
2020 |
- violate the protocol, and will fail. Notwithstanding the last |
|
2021 |
- sentence, at any point in time, though pointless, it is |
|
2022 |
- additionally permissible to read 0 bytes from the agent, or any |
|
2023 |
- number of bytes from a closed socket. |
|
2024 |
- |
|
2025 |
- Args: |
|
2026 |
- count: |
|
2027 |
- Number of bytes to read from the agent. |
|
2028 |
- flags: |
|
2029 |
- Reserved. Must be 0. |
|
2030 |
- |
|
2031 |
- Returns: |
|
2032 |
- (A chunk of) the SSH agent's response to the most recent |
|
2033 |
- request. If reading 0 bytes, or if reading from a closed |
|
2034 |
- socket, the returned chunk is always an empty byte string. |
|
2035 |
- |
|
2036 |
- Raises: |
|
2037 |
- AssertionError: |
|
2038 |
- The flags argument, if specified, must be 0. |
|
2039 |
- |
|
2040 |
- Alternatively, `recv` was called when there was no |
|
2041 |
- response to be obtained, in violation of the SSH agent |
|
2042 |
- protocol. |
|
2043 |
- |
|
2044 |
- """ |
|
2045 |
- assert not flags, self._NO_FLAG_SUPPORT |
|
2046 |
- assert not count or self.closed or self.send_to_client, ( |
|
2047 |
- self._PROTOCOL_VIOLATION |
|
2048 |
- ) |
|
2049 |
- ret = bytes(self.send_to_client[:count]) |
|
2050 |
- del self.send_to_client[:count] |
|
2051 |
- return ret |
|
2052 |
- |
|
2053 |
- def parse_client_request_and_dispatch(self) -> None: |
|
2054 |
- """Parse the client request and call the matching handler. |
|
2055 |
- |
|
2056 |
- This agent supports the |
|
2057 |
- [`SSH_AGENTC_REQUEST_IDENTITIES`][_types.SSH_AGENTC.REQUEST_IDENTITIES], |
|
2058 |
- [`SSH_AGENTC_SIGN_REQUEST`][_types.SSH_AGENTC.SIGN_REQUEST] and |
|
2059 |
- the [`SSH_AGENTC_EXTENSION`][_types.SSH_AGENTC.EXTENSION] |
|
2060 |
- request types. |
|
2061 |
- |
|
2062 |
- """ |
|
2063 |
- |
|
2064 |
- if len(self.receive_from_client) < self.HEADER_SIZE + self.CODE_SIZE: |
|
2065 |
- raise ValueError(self._INVALID_REQUEST) |
|
2066 |
- target_header = ssh_agent.SSHAgentClient.uint32( |
|
2067 |
- len(self.receive_from_client) - self.HEADER_SIZE |
|
2068 |
- ) |
|
2069 |
- if target_header != self.receive_from_client[: self.HEADER_SIZE]: |
|
2070 |
- raise ValueError(self._INVALID_REQUEST) |
|
2071 |
- code = _types.SSH_AGENTC( |
|
2072 |
- int.from_bytes( |
|
2073 |
- self.receive_from_client[ |
|
2074 |
- self.HEADER_SIZE : self.HEADER_SIZE + self.CODE_SIZE |
|
2075 |
- ], |
|
2076 |
- "big", |
|
2077 |
- ) |
|
2078 |
- ) |
|
2079 |
- |
|
2080 |
- def is_enabled_extension(extension: str) -> bool: |
|
2081 |
- if ( |
|
2082 |
- extension not in self.enabled_extensions |
|
2083 |
- or code != _types.SSH_AGENTC.EXTENSION |
|
2084 |
- ): |
|
2085 |
- return False |
|
2086 |
- string = ssh_agent.SSHAgentClient.string |
|
2087 |
- extension_marker = b"\x1b" + string(extension.encode("ascii")) |
|
2088 |
- return self.receive_from_client.startswith(extension_marker, 4) |
|
2089 |
- |
|
2090 |
- result: Buffer | Iterator[int] |
|
2091 |
- if code == _types.SSH_AGENTC.REQUEST_IDENTITIES: |
|
2092 |
- result = self.request_identities(list_extended=False) |
|
2093 |
- elif code == _types.SSH_AGENTC.SIGN_REQUEST: |
|
2094 |
- result = self.sign() |
|
2095 |
- elif is_enabled_extension("query"): |
|
2096 |
- result = self.query_extensions() |
|
2097 |
- elif is_enabled_extension("list-extended@putty.projects.tartarus.org"): |
|
2098 |
- result = self.request_identities(list_extended=True) |
|
2099 |
- else: |
|
2100 |
- raise ValueError(self._UNSUPPORTED_REQUEST) |
|
2101 |
- self.send_to_client.extend( |
|
2102 |
- ssh_agent.SSHAgentClient.string(bytes(result)) |
|
2103 |
- ) |
|
2104 |
- |
|
2105 |
- def query_extensions(self) -> Iterator[int]: |
|
2106 |
- """Answer an `SSH_AGENTC_EXTENSION` request. |
|
2107 |
- |
|
2108 |
- Yields: |
|
2109 |
- The bytes payload of the response, without the protocol |
|
2110 |
- framing. The payload is yielded byte by byte, as an |
|
2111 |
- iterable of 8-bit integers. |
|
2112 |
- |
|
2113 |
- """ |
|
2114 |
- yield _types.SSH_AGENT.EXTENSION_RESPONSE.value |
|
2115 |
- yield from ssh_agent.SSHAgentClient.string(b"query") |
|
2116 |
- extension_answers = [ |
|
2117 |
- b"query", |
|
2118 |
- b"list-extended@putty.projects.tartarus.org", |
|
2119 |
- ] |
|
2120 |
- for a in extension_answers: |
|
2121 |
- yield from ssh_agent.SSHAgentClient.string(a) |
|
2122 |
- |
|
2123 |
- def request_identities( |
|
2124 |
- self, *, list_extended: bool = False |
|
2125 |
- ) -> Iterator[int]: |
|
2126 |
- """Answer an `SSH_AGENTC_REQUEST_IDENTITIES` request. |
|
2127 |
- |
|
2128 |
- Args: |
|
2129 |
- list_extended: |
|
2130 |
- If true, answer an `SSH_AGENTC_EXTENSION` request for |
|
2131 |
- the `list-extended@putty.projects.tartarus.org` |
|
2132 |
- extension. Otherwise, answer an |
|
2133 |
- `SSH_AGENTC_REQUEST_IDENTITIES` request. |
|
2134 |
- |
|
2135 |
- Yields: |
|
2136 |
- The bytes payload of the response, without the protocol |
|
2137 |
- framing. The payload is yielded byte by byte, as an |
|
2138 |
- iterable of 8-bit integers. |
|
2139 |
- |
|
2140 |
- """ |
|
2141 |
- if list_extended: |
|
2142 |
- yield _types.SSH_AGENT.SUCCESS.value |
|
2143 |
- else: |
|
2144 |
- yield _types.SSH_AGENT.IDENTITIES_ANSWER.value |
|
2145 |
- signature_classes = [ |
|
2146 |
- SSHTestKeyDeterministicSignatureClass.SPEC, |
|
2147 |
- ] |
|
2148 |
- if ( |
|
2149 |
- "list-extended@putty.projects.tartarus.org" |
|
2150 |
- in self.enabled_extensions |
|
2151 |
- ): |
|
2152 |
- signature_classes.append( |
|
2153 |
- SSHTestKeyDeterministicSignatureClass.RFC_6979 |
|
2154 |
- ) |
|
2155 |
- keys = [ |
|
2156 |
- v |
|
2157 |
- for v in ALL_KEYS.values() |
|
2158 |
- if any(cls in v.expected_signatures for cls in signature_classes) |
|
2159 |
- ] |
|
2160 |
- yield from ssh_agent.SSHAgentClient.uint32(len(keys)) |
|
2161 |
- for key in keys: |
|
2162 |
- yield from ssh_agent.SSHAgentClient.string(key.public_key_data) |
|
2163 |
- yield from ssh_agent.SSHAgentClient.string( |
|
2164 |
- b"test key without passphrase" |
|
2165 |
- ) |
|
2166 |
- if list_extended: |
|
2167 |
- yield from ssh_agent.SSHAgentClient.string( |
|
2168 |
- ssh_agent.SSHAgentClient.uint32(0) |
|
2169 |
- ) |
|
2170 |
- |
|
2171 |
- def sign(self) -> bytes: |
|
2172 |
- """Answer an `SSH_AGENTC_SIGN_REQUEST` request. |
|
2173 |
- |
|
2174 |
- Returns: |
|
2175 |
- The bytes payload of the response, without the protocol |
|
2176 |
- framing. |
|
2177 |
- |
|
2178 |
- """ |
|
2179 |
- try_rfc6979 = ( |
|
2180 |
- "list-extended@putty.projects.tartarus.org" |
|
2181 |
- in self.enabled_extensions |
|
2182 |
- ) |
|
2183 |
- spec = SSHTestKeyDeterministicSignatureClass.SPEC |
|
2184 |
- rfc6979 = SSHTestKeyDeterministicSignatureClass.RFC_6979 |
|
2185 |
- key_blob, rest = ssh_agent.SSHAgentClient.unstring_prefix( |
|
2186 |
- self.receive_from_client[self.HEADER_SIZE + self.CODE_SIZE :] |
|
2187 |
- ) |
|
2188 |
- sign_data, rest = ssh_agent.SSHAgentClient.unstring_prefix(rest) |
|
2189 |
- if len(rest) != 4: |
|
2190 |
- raise ValueError(self._INVALID_REQUEST) |
|
2191 |
- flags = int.from_bytes(rest, "big") |
|
2192 |
- if flags: |
|
2193 |
- raise ValueError(self._UNSUPPORTED_REQUEST) |
|
2194 |
- if sign_data != vault.Vault.UUID: |
|
2195 |
- raise ValueError(self._UNSUPPORTED_REQUEST) |
|
2196 |
- for key in ALL_KEYS.values(): |
|
2197 |
- if key.public_key_data == key_blob: |
|
2198 |
- if spec in key.expected_signatures: |
|
2199 |
- return int.to_bytes( |
|
2200 |
- _types.SSH_AGENT.SIGN_RESPONSE.value, 1, "big" |
|
2201 |
- ) + ssh_agent.SSHAgentClient.string( |
|
2202 |
- key.expected_signatures[spec].signature |
|
2203 |
- ) |
|
2204 |
- if try_rfc6979 and rfc6979 in key.expected_signatures: |
|
2205 |
- return int.to_bytes( |
|
2206 |
- _types.SSH_AGENT.SIGN_RESPONSE.value, 1, "big" |
|
2207 |
- ) + ssh_agent.SSHAgentClient.string( |
|
2208 |
- key.expected_signatures[rfc6979].signature |
|
2209 |
- ) |
|
2210 |
- raise ValueError(self._UNSUPPORTED_REQUEST) |
|
2211 |
- raise ValueError(self._UNSUPPORTED_REQUEST) |
|
2212 |
- |
|
2213 |
- |
|
2214 |
-@socketprovider.SocketProvider.register("stub_with_address") |
|
2215 |
-class StubbedSSHAgentSocketWithAddress(StubbedSSHAgentSocket): |
|
2216 |
- """A [`StubbedSSHAgentSocket`][] requiring a specific address.""" |
|
2217 |
- |
|
2218 |
- ADDRESS = "stub-ssh-agent:" |
|
2219 |
- """The correct address for connecting to this stubbed agent.""" |
|
2220 |
- |
|
2221 |
- def __init__(self, *extensions: str) -> None: |
|
2222 |
- """Initialize the agent, based on `SSH_AUTH_SOCK`. |
|
2223 |
- |
|
2224 |
- Socket addresses of the form `stub-ssh-agent:<errno_value>` will |
|
2225 |
- raise an [`OSError`][] (or the respective subclass) with the |
|
2226 |
- specified [`errno`][] value. For example, |
|
2227 |
- `stub-ssh-agent:EPERM` will raise a [`PermissionError`][]. |
|
2228 |
- |
|
2229 |
- Raises: |
|
2230 |
- KeyError: |
|
2231 |
- The `SSH_AUTH_SOCK` environment variable is not set. |
|
2232 |
- OSError: |
|
2233 |
- The address in `SSH_AUTH_SOCK` is unsuited. |
|
2234 |
- |
|
2235 |
- """ |
|
2236 |
- super().__init__(*extensions) |
|
2237 |
- try: |
|
2238 |
- orig_address = os.environ["SSH_AUTH_SOCK"] |
|
2239 |
- except KeyError as exc: |
|
2240 |
- msg = "SSH_AUTH_SOCK environment variable" |
|
2241 |
- raise KeyError(msg) from exc |
|
2242 |
- address = orig_address |
|
2243 |
- if not address.startswith(self.ADDRESS): |
|
2244 |
- address = self.ADDRESS + "ENOENT" |
|
2245 |
- errcode = address.removeprefix(self.ADDRESS) |
|
2246 |
- if errcode and not ( |
|
2247 |
- errcode.startswith("E") and hasattr(errno, errcode) |
|
2248 |
- ): |
|
2249 |
- errcode = "EINVAL" |
|
2250 |
- if errcode: |
|
2251 |
- errno_val = getattr(errno, errcode) |
|
2252 |
- raise OSError(errno_val, os.strerror(errno_val), orig_address) |
|
2253 |
- |
|
2254 |
- |
|
2255 |
-@socketprovider.SocketProvider.register( |
|
2256 |
- "stub_with_address_and_deterministic_dsa" |
|
2257 |
-) |
|
2258 |
-class StubbedSSHAgentSocketWithAddressAndDeterministicDSA( |
|
2259 |
- StubbedSSHAgentSocketWithAddress |
|
2260 |
-): |
|
2261 |
- """A [`StubbedSSHAgentSocketWithAddress`][] supporting deterministic DSA.""" |
|
2262 |
- |
|
2263 |
- def __init__(self) -> None: |
|
2264 |
- """Initialize the agent. |
|
2265 |
- |
|
2266 |
- Set the supported extensions, and try issuing RFC 6979 and |
|
2267 |
- Pageant 0.68–0.80 DSA/ECDSA signatures, if possible. See the |
|
2268 |
- [superclass constructor][StubbedSSHAgentSocketWithAddress] for |
|
2269 |
- other details. |
|
2270 |
- |
|
2271 |
- Raises: |
|
2272 |
- KeyError: See superclass. |
|
2273 |
- OSError: See superclass. |
|
2274 |
- |
|
2275 |
- """ # noqa: RUF002 |
|
2276 |
- super().__init__("query", "list-extended@putty.projects.tartarus.org") |
|
2277 |
- self.try_rfc6979 = True |
|
2278 |
- self.try_pageant_068_080 = True |
|
2279 |
- |
|
2280 |
- |
|
2281 |
-def list_keys(self: Any = None) -> list[_types.SSHKeyCommentPair]: |
|
2282 |
- """Return a list of all SSH test keys, as key/comment pairs. |
|
2283 |
- |
|
2284 |
- Intended as a monkeypatching replacement for |
|
2285 |
- [`ssh_agent.SSHAgentClient.list_keys`][]. |
|
2286 |
- |
|
2287 |
- """ |
|
2288 |
- del self # Unused. |
|
2289 |
- Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
2290 |
- return [ |
|
2291 |
- Pair(value.public_key_data, f"{key} test key".encode("ASCII")) |
|
2292 |
- for key, value in ALL_KEYS.items() |
|
2293 |
- ] |
|
2294 |
- |
|
2295 |
- |
|
2296 |
-def sign( |
|
2297 |
- self: Any, key: bytes | bytearray, message: bytes | bytearray |
|
2298 |
-) -> bytes: |
|
2299 |
- """Return the signature of `message` under `key`. |
|
2300 |
- |
|
2301 |
- Can only handle keys in [`SUPPORTED_KEYS`][], and only the vault |
|
2302 |
- UUID as the message. |
|
2303 |
- |
|
2304 |
- Intended as a monkeypatching replacement for |
|
2305 |
- [`ssh_agent.SSHAgentClient.sign`][]. |
|
2306 |
- |
|
2307 |
- """ |
|
2308 |
- del self # Unused. |
|
2309 |
- assert message == vault.Vault.UUID |
|
2310 |
- for value in SUPPORTED_KEYS.values(): |
|
2311 |
- if value.public_key_data == key: # pragma: no branch |
|
2312 |
- return value.expected_signatures[ |
|
2313 |
- SSHTestKeyDeterministicSignatureClass.SPEC |
|
2314 |
- ].signature |
|
2315 |
- raise AssertionError |
|
2316 |
- |
|
2317 |
- |
|
2318 |
-def list_keys_singleton(self: Any = None) -> list[_types.SSHKeyCommentPair]: |
|
2319 |
- """Return a singleton list of the first supported SSH test key. |
|
2320 |
- |
|
2321 |
- The key is returned as a key/comment pair. |
|
2322 |
- |
|
2323 |
- Intended as a monkeypatching replacement for |
|
2324 |
- [`ssh_agent.SSHAgentClient.list_keys`][]. |
|
2325 |
- |
|
2326 |
- """ |
|
2327 |
- del self # Unused. |
|
2328 |
- Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
2329 |
- list1 = [ |
|
2330 |
- Pair(value.public_key_data, f"{key} test key".encode("ASCII")) |
|
2331 |
- for key, value in SUPPORTED_KEYS.items() |
|
2332 |
- ] |
|
2333 |
- return list1[:1] |
|
2334 |
- |
|
2335 |
- |
|
2336 |
-def suitable_ssh_keys(conn: Any) -> Iterator[_types.SSHKeyCommentPair]: |
|
2337 |
- """Return a two-item list of SSH test keys (key/comment pairs). |
|
2338 |
- |
|
2339 |
- Intended as a monkeypatching replacement for |
|
2340 |
- `cli_machinery.get_suitable_ssh_keys` to better script and test the |
|
2341 |
- interactive key selection. When used this way, `derivepassphrase` |
|
2342 |
- believes that only those two keys are loaded and suitable. |
|
2343 |
- |
|
2344 |
- """ |
|
2345 |
- del conn # Unused. |
|
2346 |
- Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
2347 |
- yield from [ |
|
2348 |
- Pair(DUMMY_KEY1, b"no comment"), |
|
2349 |
- Pair(DUMMY_KEY2, b"a comment"), |
|
2350 |
- ] |
|
2351 |
- |
|
2352 |
- |
|
2353 |
-def phrase_from_key( |
|
2354 |
- key: bytes, |
|
2355 |
- /, |
|
2356 |
- *, |
|
2357 |
- conn: ssh_agent.SSHAgentClient | socket.socket | None = None, |
|
2358 |
-) -> bytes: |
|
2359 |
- """Return the "equivalent master passphrase" for key. |
|
2360 |
- |
|
2361 |
- Only works for key [`DUMMY_KEY1`][]. |
|
2362 |
- |
|
2363 |
- Intended as a monkeypatching replacement for |
|
2364 |
- [`vault.Vault.phrase_from_key`][], bypassing communication with an |
|
2365 |
- actual SSH agent. |
|
2366 |
- |
|
2367 |
- """ |
|
2368 |
- del conn |
|
2369 |
- if key == DUMMY_KEY1: # pragma: no branch |
|
2370 |
- return DUMMY_PHRASE_FROM_KEY1 |
|
2371 |
- raise KeyError(key) # pragma: no cover |
|
2372 |
- |
|
2373 |
- |
|
2374 |
-def provider_entry_provider() -> _types.SSHAgentSocket: # pragma: no cover |
|
2375 |
- """A pseudo provider for a [`_types.SSHAgentSocketProviderEntry`][].""" |
|
2376 |
- msg = "We are not supposed to be called!" |
|
2377 |
- raise AssertionError(msg) |
|
2378 |
- |
|
2379 |
- |
|
2380 |
-provider_entry1 = _types.SSHAgentSocketProviderEntry( |
|
2381 |
- provider_entry_provider, "entry1", ("entry1a", "entry1b", "entry1c") |
|
2382 |
-) |
|
2383 |
-"""A sample [`_types.SSHAgentSocketProviderEntry`][].""" |
|
2384 |
- |
|
2385 |
-provider_entry2 = _types.SSHAgentSocketProviderEntry( |
|
2386 |
- provider_entry_provider, "entry2", ("entry2d", "entry2e") |
|
2387 |
-) |
|
2388 |
-"""A sample [`_types.SSHAgentSocketProviderEntry`][].""" |
|
2389 |
- |
|
2390 |
-posix_entry = _types.SSHAgentSocketProviderEntry( |
|
2391 |
- socketprovider.SocketProvider.resolve("posix"), "posix", () |
|
2392 |
-) |
|
2393 |
-""" |
|
2394 |
-The standard [`_types.SSHAgentSocketProviderEntry`][] for the UNIX |
|
2395 |
-domain socket handler on POSIX systems. |
|
2396 |
-""" |
|
2397 |
- |
|
2398 |
-the_annoying_os_entry = _types.SSHAgentSocketProviderEntry( |
|
2399 |
- socketprovider.SocketProvider.resolve("the_annoying_os"), |
|
2400 |
- "the_annoying_os", |
|
2401 |
- (), |
|
2402 |
-) |
|
2403 |
-""" |
|
2404 |
-The standard [`_types.SSHAgentSocketProviderEntry`][] for the named pipe |
|
2405 |
-handler on The Annoying Operating System. |
|
2406 |
-""" |
|
2407 |
- |
|
2408 |
-faulty_entry_callable = _types.SSHAgentSocketProviderEntry( |
|
2409 |
- (), # type: ignore[arg-type] |
|
2410 |
- "tuple", |
|
2411 |
- (), |
|
2412 |
-) |
|
2413 |
-""" |
|
2414 |
-A faulty [`_types.SSHAgentSocketProviderEntry`][]: the indicated handler |
|
2415 |
-is not a callable. |
|
2416 |
-""" |
|
2417 |
- |
|
2418 |
-faulty_entry_name_exists = _types.SSHAgentSocketProviderEntry( |
|
2419 |
- socketprovider.SocketProvider.resolve("the_annoying_os"), "posix", () |
|
2420 |
-) |
|
2421 |
-""" |
|
2422 |
-A faulty [`_types.SSHAgentSocketProviderEntry`][]: the indicated handler |
|
2423 |
-is already registered with a different callable. |
|
2424 |
-""" |
|
2425 |
- |
|
2426 |
-faulty_entry_alias_exists = _types.SSHAgentSocketProviderEntry( |
|
2427 |
- socketprovider.SocketProvider.resolve("posix"), |
|
2428 |
- "posix", |
|
2429 |
- ("unix_domain", "the_annoying_os"), |
|
2430 |
-) |
|
2431 |
-""" |
|
2432 |
-A faulty [`_types.SSHAgentSocketProviderEntry`][]: the alias is already |
|
2433 |
-registered with a different callable. |
|
2434 |
-""" |
|
2435 |
- |
|
2436 |
- |
|
2437 |
-@contextlib.contextmanager |
|
2438 |
-def faked_entry_point_list( # noqa: C901 |
|
2439 |
- additional_entry_points: Sequence[importlib.metadata.EntryPoint], |
|
2440 |
- remove_conflicting_entries: bool = False, |
|
2441 |
-) -> Iterator[Sequence[str]]: |
|
2442 |
- """Yield a context where additional entry points are visible. |
|
2443 |
- |
|
2444 |
- Args: |
|
2445 |
- additional_entry_points: |
|
2446 |
- A sequence of entry point objects that should additionally |
|
2447 |
- be visible. |
|
2448 |
- remove_conflicting_entries: |
|
2449 |
- If true, remove all names provided by the additional entry |
|
2450 |
- points, otherwise leave them untouched. |
|
2451 |
- |
|
2452 |
- Yields: |
|
2453 |
- A sequence of registry names that are newly available within the |
|
2454 |
- context. |
|
2455 |
- |
|
2456 |
- """ |
|
2457 |
- true_entry_points = importlib.metadata.entry_points() |
|
2458 |
- additional_entry_points = list(additional_entry_points) |
|
2459 |
- |
|
2460 |
- if sys.version_info >= (3, 12): |
|
2461 |
- new_entry_points = importlib.metadata.EntryPoints( |
|
2462 |
- list(true_entry_points) + additional_entry_points |
|
2463 |
- ) |
|
2464 |
- |
|
2465 |
- @overload |
|
2466 |
- def mangled_entry_points( |
|
2467 |
- *, group: None = None |
|
2468 |
- ) -> importlib.metadata.EntryPoints: ... |
|
2469 |
- |
|
2470 |
- @overload |
|
2471 |
- def mangled_entry_points( |
|
2472 |
- *, group: str |
|
2473 |
- ) -> importlib.metadata.EntryPoints: ... |
|
2474 |
- |
|
2475 |
- def mangled_entry_points( |
|
2476 |
- **params: Any, |
|
2477 |
- ) -> importlib.metadata.EntryPoints: |
|
2478 |
- return new_entry_points.select(**params) |
|
2479 |
- |
|
2480 |
- elif sys.version_info >= (3, 10): |
|
2481 |
- # Compatibility concerns within importlib.metadata: depending on |
|
2482 |
- # whether the .select() API is used, the result is either the dict |
|
2483 |
- # of groups of points (as in < 3.10), or the EntryPoints iterable |
|
2484 |
- # (as in >= 3.12). So our wrapper needs to duplicate that |
|
2485 |
- # interface. FUN. |
|
2486 |
- new_entry_points_dict = { |
|
2487 |
- k: list(v) for k, v in true_entry_points.items() |
|
2488 |
- } |
|
2489 |
- for ep in additional_entry_points: |
|
2490 |
- new_entry_points_dict.setdefault(ep.group, []).append(ep) |
|
2491 |
- new_entry_points = importlib.metadata.EntryPoints([ |
|
2492 |
- ep for group in new_entry_points_dict.values() for ep in group |
|
2493 |
- ]) |
|
2494 |
- |
|
2495 |
- @overload |
|
2496 |
- def mangled_entry_points( |
|
2497 |
- *, group: None = None |
|
2498 |
- ) -> dict[ |
|
2499 |
- str, |
|
2500 |
- list[importlib.metadata.EntryPoint] |
|
2501 |
- | tuple[importlib.metadata.EntryPoint, ...], |
|
2502 |
- ]: ... |
|
2503 |
- |
|
2504 |
- @overload |
|
2505 |
- def mangled_entry_points( |
|
2506 |
- *, group: str |
|
2507 |
- ) -> importlib.metadata.EntryPoints: ... |
|
2508 |
- |
|
2509 |
- def mangled_entry_points( |
|
2510 |
- **params: Any, |
|
2511 |
- ) -> ( |
|
2512 |
- importlib.metadata.EntryPoints |
|
2513 |
- | dict[ |
|
2514 |
- str, |
|
2515 |
- list[importlib.metadata.EntryPoint] |
|
2516 |
- | tuple[importlib.metadata.EntryPoint, ...], |
|
2517 |
- ] |
|
2518 |
- ): |
|
2519 |
- return ( |
|
2520 |
- new_entry_points.select(**params) |
|
2521 |
- if params |
|
2522 |
- else new_entry_points_dict |
|
2523 |
- ) |
|
2524 |
- |
|
2525 |
- else: |
|
2526 |
- new_entry_points: dict[ |
|
2527 |
- str, |
|
2528 |
- list[importlib.metadata.EntryPoint] |
|
2529 |
- | tuple[importlib.metadata.EntryPoint, ...], |
|
2530 |
- ] = { |
|
2531 |
- group_name: list(group) |
|
2532 |
- for group_name, group in true_entry_points.items() |
|
2533 |
- } |
|
2534 |
- for ep in additional_entry_points: |
|
2535 |
- new_entry_points.setdefault(ep.group, []) |
|
2536 |
- new_entry_points[ep.group].append(ep) |
|
2537 |
- new_entry_points = { |
|
2538 |
- group_name: tuple(group) |
|
2539 |
- for group_name, group in new_entry_points.items() |
|
2540 |
- } |
|
2541 |
- |
|
2542 |
- @overload |
|
2543 |
- def mangled_entry_points( |
|
2544 |
- *, group: None = None |
|
2545 |
- ) -> dict[str, tuple[importlib.metadata.EntryPoint, ...]]: ... |
|
2546 |
- |
|
2547 |
- @overload |
|
2548 |
- def mangled_entry_points( |
|
2549 |
- *, group: str |
|
2550 |
- ) -> tuple[importlib.metadata.EntryPoint, ...]: ... |
|
2551 |
- |
|
2552 |
- def mangled_entry_points( |
|
2553 |
- *, group: str | None = None |
|
2554 |
- ) -> ( |
|
2555 |
- dict[str, tuple[importlib.metadata.EntryPoint, ...]] |
|
2556 |
- | tuple[importlib.metadata.EntryPoint, ...] |
|
2557 |
- ): |
|
2558 |
- return ( |
|
2559 |
- new_entry_points.get(group, ()) |
|
2560 |
- if group is not None |
|
2561 |
- else new_entry_points |
|
2562 |
- ) |
|
2563 |
- |
|
2564 |
- registry = socketprovider.SocketProvider.registry |
|
2565 |
- new_registry = registry.copy() |
|
2566 |
- keys = [ep.load().key for ep in additional_entry_points] |
|
2567 |
- aliases = [a for ep in additional_entry_points for a in ep.load().aliases] |
|
2568 |
- if remove_conflicting_entries: # pragma: no cover [unused] |
|
2569 |
- for name in [*keys, *aliases]: |
|
2570 |
- new_registry.pop(name, None) |
|
2571 |
- |
|
2572 |
- with pytest.MonkeyPatch.context() as monkeypatch: |
|
2573 |
- monkeypatch.setattr( |
|
2574 |
- socketprovider.SocketProvider, "registry", new_registry |
|
2575 |
- ) |
|
2576 |
- monkeypatch.setattr( |
|
2577 |
- importlib.metadata, "entry_points", mangled_entry_points |
|
2578 |
- ) |
|
2579 |
- yield (*keys, *aliases) |
|
2580 |
- |
|
2581 |
- |
|
2582 |
-@contextlib.contextmanager |
|
2583 |
-def isolated_config( |
|
2584 |
- monkeypatch: pytest.MonkeyPatch, |
|
2585 |
- runner: CliRunner, |
|
2586 |
- main_config_str: str | None = None, |
|
2587 |
-) -> Iterator[None]: |
|
2588 |
- """Provide an isolated configuration setup, as a context. |
|
2589 |
- |
|
2590 |
- This context manager sets up (and changes into) a temporary |
|
2591 |
- directory, which holds the user configuration specified in |
|
2592 |
- `main_config_str`, if any. The manager also ensures that the |
|
2593 |
- environment variables `HOME` and `USERPROFILE` are set, and that |
|
2594 |
- `DERIVEPASSPHRASE_PATH` is unset. Upon exiting the context, the |
|
2595 |
- changes are undone and the temporary directory is removed. |
|
2596 |
- |
|
2597 |
- Args: |
|
2598 |
- monkeypatch: |
|
2599 |
- A monkeypatch fixture object. |
|
2600 |
- runner: |
|
2601 |
- A `click` CLI runner harness. |
|
2602 |
- main_config_str: |
|
2603 |
- Optional TOML file contents, to be used as the user |
|
2604 |
- configuration. |
|
2605 |
- |
|
2606 |
- Returns: |
|
2607 |
- A context manager, without a return value. |
|
2608 |
- |
|
2609 |
- """ |
|
2610 |
- prog_name = cli_helpers.PROG_NAME |
|
2611 |
- env_name = prog_name.replace(" ", "_").upper() + "_PATH" |
|
2612 |
- # TODO(the-13th-letter): Rewrite using parenthesized with-statements. |
|
2613 |
- # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2614 |
- with contextlib.ExitStack() as stack: |
|
2615 |
- stack.enter_context(runner.isolated_filesystem()) |
|
2616 |
- stack.enter_context( |
|
2617 |
- cli_machinery.StandardCLILogging.ensure_standard_logging() |
|
2618 |
- ) |
|
2619 |
- stack.enter_context( |
|
2620 |
- cli_machinery.StandardCLILogging.ensure_standard_warnings_logging() |
|
2621 |
- ) |
|
2622 |
- cwd = str(pathlib.Path.cwd().resolve()) |
|
2623 |
- monkeypatch.setenv("HOME", cwd) |
|
2624 |
- monkeypatch.setenv("APPDATA", cwd) |
|
2625 |
- monkeypatch.setenv("LOCALAPPDATA", cwd) |
|
2626 |
- monkeypatch.delenv(env_name, raising=False) |
|
2627 |
- config_dir = cli_helpers.config_filename(subsystem=None) |
|
2628 |
- config_dir.mkdir(parents=True, exist_ok=True) |
|
2629 |
- if isinstance(main_config_str, str): |
|
2630 |
- cli_helpers.config_filename("user configuration").write_text( |
|
2631 |
- main_config_str, encoding="UTF-8" |
|
2632 |
- ) |
|
2633 |
- try: |
|
2634 |
- yield |
|
2635 |
- finally: |
|
2636 |
- cli_helpers.config_filename("write lock").unlink(missing_ok=True) |
|
2637 |
- |
|
2638 |
- |
|
2639 |
-@contextlib.contextmanager |
|
2640 |
-def isolated_vault_config( |
|
2641 |
- monkeypatch: pytest.MonkeyPatch, |
|
2642 |
- runner: CliRunner, |
|
2643 |
- vault_config: Any, |
|
2644 |
- main_config_str: str | None = None, |
|
2645 |
-) -> Iterator[None]: |
|
2646 |
- """Provide an isolated vault configuration setup, as a context. |
|
2647 |
- |
|
2648 |
- Uses [`isolated_config`][] internally. Beyond those actions, this |
|
2649 |
- manager also loads the specified vault configuration into the |
|
2650 |
- context. |
|
2651 |
- |
|
2652 |
- Args: |
|
2653 |
- monkeypatch: |
|
2654 |
- A monkeypatch fixture object. |
|
2655 |
- runner: |
|
2656 |
- A `click` CLI runner harness. |
|
2657 |
- vault_config: |
|
2658 |
- A valid vault configuration, to be integrated into the |
|
2659 |
- context. |
|
2660 |
- main_config_str: |
|
2661 |
- Optional TOML file contents, to be used as the user |
|
2662 |
- configuration. |
|
2663 |
- |
|
2664 |
- Returns: |
|
2665 |
- A context manager, without a return value. |
|
2666 |
- |
|
2667 |
- """ |
|
2668 |
- with isolated_config( |
|
2669 |
- monkeypatch=monkeypatch, runner=runner, main_config_str=main_config_str |
|
2670 |
- ): |
|
2671 |
- config_filename = cli_helpers.config_filename(subsystem="vault") |
|
2672 |
- with config_filename.open("w", encoding="UTF-8") as outfile: |
|
2673 |
- json.dump(vault_config, outfile) |
|
2674 |
- yield |
|
2675 |
- |
|
2676 |
- |
|
2677 |
-@contextlib.contextmanager |
|
2678 |
-def isolated_vault_exporter_config( |
|
2679 |
- monkeypatch: pytest.MonkeyPatch, |
|
2680 |
- runner: CliRunner, |
|
2681 |
- vault_config: str | bytes | None = None, |
|
2682 |
- vault_key: str | None = None, |
|
2683 |
-) -> Iterator[None]: |
|
2684 |
- """Provide an isolated vault configuration setup, as a context. |
|
2685 |
- |
|
2686 |
- Works similarly to [`isolated_config`][], except that no user |
|
2687 |
- configuration is accepted or integrated into the context. This |
|
2688 |
- manager also accepts a serialized vault-native configuration and |
|
2689 |
- a vault encryption key to integrate into the context. |
|
2690 |
- |
|
2691 |
- Args: |
|
2692 |
- monkeypatch: |
|
2693 |
- A monkeypatch fixture object. |
|
2694 |
- runner: |
|
2695 |
- A `click` CLI runner harness. |
|
2696 |
- vault_config: |
|
2697 |
- An optional serialized vault-native configuration, to be |
|
2698 |
- integrated into the context. If a text string, then the |
|
2699 |
- contents are written to the file `.vault`. If a byte |
|
2700 |
- string, then it is treated as base64-encoded zip file |
|
2701 |
- contents, which---once inside the `.vault` directory---will |
|
2702 |
- be extracted into the current directory. |
|
2703 |
- vault_key: |
|
2704 |
- An optional encryption key presumably for the stored |
|
2705 |
- vault-native configuration. If given, then the environment |
|
2706 |
- variable `VAULT_KEY` will be populated with this key while |
|
2707 |
- the context is active. |
|
2708 |
- |
|
2709 |
- Returns: |
|
2710 |
- A context manager, without a return value. |
|
2711 |
- |
|
2712 |
- """ |
|
2713 |
- # TODO(the-13th-letter): Remove the fallback implementation. |
|
2714 |
- # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.10 |
|
2715 |
- if TYPE_CHECKING: |
|
2716 |
- chdir: Callable[..., AbstractContextManager] |
|
2717 |
- else: |
|
2718 |
- try: |
|
2719 |
- chdir = contextlib.chdir # type: ignore[attr] |
|
2720 |
- except AttributeError: |
|
2721 |
- |
|
2722 |
- @contextlib.contextmanager |
|
2723 |
- def chdir( |
|
2724 |
- newpath: str | bytes | os.PathLike, |
|
2725 |
- ) -> Iterator[None]: # pragma: no branch |
|
2726 |
- oldpath = pathlib.Path.cwd().resolve() |
|
2727 |
- os.chdir(newpath) |
|
2728 |
- yield |
|
2729 |
- os.chdir(oldpath) |
|
2730 |
- |
|
2731 |
- with runner.isolated_filesystem(): |
|
2732 |
- cwd = str(pathlib.Path.cwd().resolve()) |
|
2733 |
- monkeypatch.setenv("HOME", cwd) |
|
2734 |
- monkeypatch.setenv("USERPROFILE", cwd) |
|
2735 |
- monkeypatch.delenv( |
|
2736 |
- cli_helpers.PROG_NAME.replace(" ", "_").upper() + "_PATH", |
|
2737 |
- raising=False, |
|
2738 |
- ) |
|
2739 |
- monkeypatch.delenv("VAULT_PATH", raising=False) |
|
2740 |
- monkeypatch.delenv("VAULT_KEY", raising=False) |
|
2741 |
- monkeypatch.delenv("LOGNAME", raising=False) |
|
2742 |
- monkeypatch.delenv("USER", raising=False) |
|
2743 |
- monkeypatch.delenv("USERNAME", raising=False) |
|
2744 |
- if vault_key is not None: |
|
2745 |
- monkeypatch.setenv("VAULT_KEY", vault_key) |
|
2746 |
- vault_config_path = pathlib.Path(".vault").resolve() |
|
2747 |
- # TODO(the-13th-letter): Rewrite using structural pattern matching. |
|
2748 |
- # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2749 |
- if isinstance(vault_config, str): |
|
2750 |
- vault_config_path.write_text(f"{vault_config}\n", encoding="UTF-8") |
|
2751 |
- elif isinstance(vault_config, bytes): |
|
2752 |
- vault_config_path.mkdir(parents=True, mode=0o700, exist_ok=True) |
|
2753 |
- # TODO(the-13th-letter): Rewrite using parenthesized |
|
2754 |
- # with-statements. |
|
2755 |
- # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2756 |
- with contextlib.ExitStack() as stack: |
|
2757 |
- stack.enter_context(chdir(vault_config_path)) |
|
2758 |
- tmpzipfile = stack.enter_context( |
|
2759 |
- tempfile.NamedTemporaryFile(suffix=".zip") |
|
2760 |
- ) |
|
2761 |
- for line in vault_config.splitlines(): |
|
2762 |
- tmpzipfile.write(base64.standard_b64decode(line)) |
|
2763 |
- tmpzipfile.flush() |
|
2764 |
- tmpzipfile.seek(0, 0) |
|
2765 |
- with zipfile.ZipFile(tmpzipfile.file) as zipfileobj: |
|
2766 |
- zipfileobj.extractall() |
|
2767 |
- elif vault_config is None: |
|
2768 |
- pass |
|
2769 |
- else: # pragma: no cover |
|
2770 |
- assert_never(vault_config) |
|
2771 |
- try: |
|
2772 |
- yield |
|
2773 |
- finally: |
|
2774 |
- cli_helpers.config_filename("write lock").unlink(missing_ok=True) |
|
2775 |
- |
|
2776 |
- |
|
2777 |
-def auto_prompt(*args: Any, **kwargs: Any) -> str: |
|
2778 |
- """Return [`DUMMY_PASSPHRASE`][]. |
|
2779 |
- |
|
2780 |
- Intended as a monkeypatching replacement for |
|
2781 |
- `cli.prompt_for_passphrase` to better script and test the |
|
2782 |
- interactive passphrase queries. |
|
2783 |
- |
|
2784 |
- """ |
|
2785 |
- del args, kwargs # Unused. |
|
2786 |
- return DUMMY_PASSPHRASE |
|
2787 |
- |
|
2788 |
- |
|
2789 |
-def make_file_readonly( |
|
2790 |
- pathname: str | bytes | os.PathLike[str], |
|
2791 |
- /, |
|
2792 |
- *, |
|
2793 |
- try_race_free_implementation: bool = True, |
|
2794 |
-) -> None: |
|
2795 |
- """Mark a file as read-only. |
|
2796 |
- |
|
2797 |
- On POSIX, this entails removing the write permission bits for user, |
|
2798 |
- group and other, and ensuring the read permission bit for user is |
|
2799 |
- set. |
|
2800 |
- |
|
2801 |
- Unfortunately, The Annoying OS (a.k.a. Microsoft Windows) has its |
|
2802 |
- own rules: Set exactly(?) the read permission bit for user to make |
|
2803 |
- the file read-only, and set exactly(?) the write permission bit for |
|
2804 |
- user to make the file read/write; all other permission bit settings |
|
2805 |
- are ignored. |
|
2806 |
- |
|
2807 |
- The cross-platform procedure therefore is: |
|
2808 |
- |
|
2809 |
- 1. Call `os.stat` on the file, noting the permission bits. |
|
2810 |
- 2. Calculate the new permission bits POSIX-style. |
|
2811 |
- 3. Call `os.chmod` with permission bit `stat.S_IREAD`. |
|
2812 |
- 4. Call `os.chmod` with the correct POSIX-style permissions. |
|
2813 |
- |
|
2814 |
- If the platform supports it, we use a file descriptor instead of |
|
2815 |
- a path name. Otherwise, we use the same path name multiple times, |
|
2816 |
- and are susceptible to race conditions. |
|
2817 |
- |
|
2818 |
- """ |
|
2819 |
- fname: int | str | bytes | os.PathLike |
|
2820 |
- if try_race_free_implementation and {os.stat, os.chmod} <= os.supports_fd: |
|
2821 |
- # The Annoying OS (v11 at least) supports fstat and fchmod, but |
|
2822 |
- # does not support changing the file mode on file descriptors |
|
2823 |
- # for read-only files. |
|
2824 |
- fname = os.open( |
|
2825 |
- pathname, |
|
2826 |
- os.O_RDWR |
|
2827 |
- | getattr(os, "O_CLOEXEC", 0) |
|
2828 |
- | getattr(os, "O_NOCTTY", 0), |
|
2829 |
- ) |
|
2830 |
- else: |
|
2831 |
- fname = pathname |
|
2832 |
- try: |
|
2833 |
- orig_mode = os.stat(fname).st_mode # noqa: PTH116 |
|
2834 |
- new_mode = ( |
|
2835 |
- orig_mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH |
|
2836 |
- | stat.S_IREAD |
|
2837 |
- ) |
|
2838 |
- os.chmod(fname, stat.S_IREAD) # noqa: PTH101 |
|
2839 |
- os.chmod(fname, new_mode) # noqa: PTH101 |
|
2840 |
- finally: |
|
2841 |
- if isinstance(fname, int): |
|
2842 |
- os.close(fname) |
|
2843 |
- |
|
2844 |
- |
|
2845 |
-class ReadableResult(NamedTuple): |
|
2846 |
- """Helper class for formatting and testing click.testing.Result objects.""" |
|
2847 |
- |
|
2848 |
- exception: BaseException | None |
|
2849 |
- exit_code: int |
|
2850 |
- stdout: str |
|
2851 |
- stderr: str |
|
2852 |
- |
|
2853 |
- def clean_exit( |
|
2854 |
- self, *, output: str = "", empty_stderr: bool = False |
|
2855 |
- ) -> bool: |
|
2856 |
- """Return whether the invocation exited cleanly. |
|
2857 |
- |
|
2858 |
- Args: |
|
2859 |
- output: |
|
2860 |
- An expected output string. |
|
2861 |
- |
|
2862 |
- """ |
|
2863 |
- return ( |
|
2864 |
- ( |
|
2865 |
- not self.exception |
|
2866 |
- or ( |
|
2867 |
- isinstance(self.exception, SystemExit) |
|
2868 |
- and self.exit_code == 0 |
|
2869 |
- ) |
|
2870 |
- ) |
|
2871 |
- and (not output or output in self.stdout) |
|
2872 |
- and (not empty_stderr or not self.stderr) |
|
2873 |
- ) |
|
2874 |
- |
|
2875 |
- def error_exit( |
|
2876 |
- self, |
|
2877 |
- *, |
|
2878 |
- error: str | re.Pattern[str] | type[BaseException] = BaseException, |
|
2879 |
- record_tuples: Sequence[tuple[str, int, str]] = (), |
|
2880 |
- ) -> bool: |
|
2881 |
- """Return whether the invocation exited uncleanly. |
|
2882 |
- |
|
2883 |
- Args: |
|
2884 |
- error: |
|
2885 |
- An expected error message, or an expected numeric error |
|
2886 |
- code, or an expected exception type. |
|
2887 |
- |
|
2888 |
- """ |
|
2889 |
- |
|
2890 |
- def error_match(error: str | re.Pattern[str], line: str) -> bool: |
|
2891 |
- return ( |
|
2892 |
- error in line |
|
2893 |
- if isinstance(error, str) |
|
2894 |
- else error.match(line) is not None |
|
2895 |
- ) |
|
2896 |
- |
|
2897 |
- # TODO(the-13th-letter): Rewrite using structural pattern matching. |
|
2898 |
- # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2899 |
- if isinstance(error, type): |
|
2900 |
- return isinstance(self.exception, error) |
|
2901 |
- else: # noqa: RET505 |
|
2902 |
- assert isinstance(error, (str, re.Pattern)) |
|
2903 |
- return ( |
|
2904 |
- isinstance(self.exception, SystemExit) |
|
2905 |
- and self.exit_code > 0 |
|
2906 |
- and ( |
|
2907 |
- not error |
|
2908 |
- or any( |
|
2909 |
- error_match(error, line) |
|
2910 |
- for line in self.stderr.splitlines(True) |
|
2911 |
- ) |
|
2912 |
- or error_emitted(error, record_tuples) |
|
2913 |
- ) |
|
2914 |
- ) |
|
2915 |
- |
|
2916 |
- |
|
2917 |
-class CliRunner: |
|
2918 |
- """An abstracted CLI runner class. |
|
2919 |
- |
|
2920 |
- Intended to provide similar functionality and scope as the |
|
2921 |
- [`click.testing.CliRunner`][] class, though not necessarily |
|
2922 |
- `click`-specific. Also allows for seamless migration away from |
|
2923 |
- `click`, if/when we decide this. |
|
2924 |
- |
|
2925 |
- """ |
|
2926 |
- |
|
2927 |
- _SUPPORTS_MIX_STDERR_ATTRIBUTE = not hasattr(click.testing, "StreamMixer") |
|
2928 |
- """ |
|
2929 |
- True if and only if [`click.testing.CliRunner`][] supports the |
|
2930 |
- `mix_stderr` attribute. It was removed in 8.2.0 in favor of the |
|
2931 |
- `click.testing.StreamMixer` class. |
|
2932 |
- |
|
2933 |
- See also |
|
2934 |
- [`pallets/click#2523`](https://github.com/pallets/click/pull/2523). |
|
2935 |
- """ |
|
2936 |
- |
|
2937 |
- def __init__( |
|
2938 |
- self, |
|
2939 |
- *, |
|
2940 |
- mix_stderr: bool = False, |
|
2941 |
- color: bool | None = None, |
|
2942 |
- ) -> None: |
|
2943 |
- self.color = color |
|
2944 |
- self.mix_stderr = mix_stderr |
|
2945 |
- |
|
2946 |
- class MixStderrAttribute(TypedDict): |
|
2947 |
- mix_stderr: NotRequired[bool] |
|
2948 |
- |
|
2949 |
- mix_stderr_args: MixStderrAttribute = ( |
|
2950 |
- {"mix_stderr": mix_stderr} |
|
2951 |
- if self._SUPPORTS_MIX_STDERR_ATTRIBUTE |
|
2952 |
- else {} |
|
2953 |
- ) |
|
2954 |
- self.click_testing_clirunner = click.testing.CliRunner( |
|
2955 |
- **mix_stderr_args |
|
2956 |
- ) |
|
2957 |
- |
|
2958 |
- def invoke( |
|
2959 |
- self, |
|
2960 |
- cli: click.BaseCommand, |
|
2961 |
- args: Sequence[str] | str | None = None, |
|
2962 |
- input: str | bytes | IO[Any] | None = None, |
|
2963 |
- env: Mapping[str, str | None] | None = None, |
|
2964 |
- catch_exceptions: bool = True, |
|
2965 |
- color: bool | None = None, |
|
2966 |
- **extra: Any, |
|
2967 |
- ) -> ReadableResult: |
|
2968 |
- if color is None: # pragma: no cover |
|
2969 |
- color = self.color if self.color is not None else False |
|
2970 |
- raw_result = self.click_testing_clirunner.invoke( |
|
2971 |
- cli, |
|
2972 |
- args=args, |
|
2973 |
- input=input, |
|
2974 |
- env=env, |
|
2975 |
- catch_exceptions=catch_exceptions, |
|
2976 |
- color=color, |
|
2977 |
- **extra, |
|
2978 |
- ) |
|
2979 |
- # In 8.2.0, r.stdout is no longer a property aliasing the |
|
2980 |
- # `output` attribute, but rather the raw stdout value. |
|
2981 |
- try: |
|
2982 |
- stderr = raw_result.stderr |
|
2983 |
- except ValueError: |
|
2984 |
- stderr = raw_result.stdout |
|
2985 |
- return ReadableResult( |
|
2986 |
- raw_result.exception, |
|
2987 |
- raw_result.exit_code, |
|
2988 |
- (raw_result.stdout if not self.mix_stderr else raw_result.output) |
|
2989 |
- or "", |
|
2990 |
- stderr or "", |
|
2991 |
- ) |
|
2992 |
- return ReadableResult.parse(raw_result) |
|
2993 |
- |
|
2994 |
- def isolated_filesystem( |
|
2995 |
- self, |
|
2996 |
- temp_dir: str | os.PathLike[str] | None = None, |
|
2997 |
- ) -> AbstractContextManager[str]: |
|
2998 |
- return self.click_testing_clirunner.isolated_filesystem( |
|
2999 |
- temp_dir=temp_dir |
|
3000 |
- ) |
|
3001 |
- |
|
3002 |
- |
|
3003 |
-def parse_sh_export_line(line: str, *, env_name: str) -> str: |
|
3004 |
- """Parse the output of typical SSH agents' SSH_AUTH_SOCK lines. |
|
3005 |
- |
|
3006 |
- Intentionally parses only a small subset of sh(1) syntax which works |
|
3007 |
- with current OpenSSH and PuTTY output. We require exactly one |
|
3008 |
- variable setting, and one export instruction, both on the same line, |
|
3009 |
- and perhaps combined into one statement. Terminating semicolons |
|
3010 |
- after each command are ignored. |
|
3011 |
- |
|
3012 |
- Args: |
|
3013 |
- line: |
|
3014 |
- A line of sh(1) script to parse. |
|
3015 |
- env_name: |
|
3016 |
- The name of the environment variable to expect. |
|
3017 |
- |
|
3018 |
- Returns: |
|
3019 |
- The parsed environment variable value. |
|
3020 |
- |
|
3021 |
- Raises: |
|
3022 |
- ValueError: |
|
3023 |
- Cannot parse the sh script. Perhaps it is too complex, |
|
3024 |
- perhaps it is malformed. |
|
3025 |
- |
|
3026 |
- """ |
|
3027 |
- line = line.rstrip("\r\n") |
|
3028 |
- shlex_parser = shlex.shlex( |
|
3029 |
- instream=line, posix=True, punctuation_chars=True |
|
3030 |
- ) |
|
3031 |
- shlex_parser.whitespace = " \t" |
|
3032 |
- tokens = list(shlex_parser) |
|
3033 |
- orig_tokens = tokens.copy() |
|
3034 |
- if tokens[-1] == ";": |
|
3035 |
- tokens.pop() |
|
3036 |
- if tokens[-3:] == [";", "export", env_name]: |
|
3037 |
- tokens[-3:] = [] |
|
3038 |
- tokens[:0] = ["export"] |
|
3039 |
- if not ( |
|
3040 |
- len(tokens) == 2 |
|
3041 |
- and tokens[0] == "export" |
|
3042 |
- and tokens[1].startswith(f"{env_name}=") |
|
3043 |
- ): |
|
3044 |
- msg = f"Cannot parse sh line: {orig_tokens!r} -> {tokens!r}" |
|
3045 |
- raise ValueError(msg) |
|
3046 |
- return tokens[1].split("=", 1)[1] |
|
3047 |
- |
|
3048 |
- |
|
3049 |
-def message_emitted_factory( |
|
3050 |
- level: int, |
|
3051 |
- *, |
|
3052 |
- logger_name: str = cli.PROG_NAME, |
|
3053 |
-) -> Callable[[str | re.Pattern[str], Sequence[tuple[str, int, str]]], bool]: |
|
3054 |
- """Return a function to test if a matching message was emitted. |
|
3055 |
- |
|
3056 |
- Args: |
|
3057 |
- level: The level to match messages at. |
|
3058 |
- logger_name: The name of the logger to match against. |
|
3059 |
- |
|
3060 |
- """ |
|
3061 |
- |
|
3062 |
- def message_emitted( |
|
3063 |
- text: str | re.Pattern[str], |
|
3064 |
- record_tuples: Sequence[tuple[str, int, str]], |
|
3065 |
- ) -> bool: |
|
3066 |
- """Return true if a matching message was emitted. |
|
3067 |
- |
|
3068 |
- Args: |
|
3069 |
- text: Substring or pattern to match against. |
|
3070 |
- record_tuples: Items to match. |
|
3071 |
- |
|
3072 |
- """ |
|
3073 |
- |
|
3074 |
- def check_record(record: tuple[str, int, str]) -> bool: |
|
3075 |
- if record[:2] != (logger_name, level): |
|
3076 |
- return False |
|
3077 |
- if isinstance(text, str): |
|
3078 |
- return text in record[2] |
|
3079 |
- return text.match(record[2]) is not None # pragma: no cover |
|
3080 |
- |
|
3081 |
- return any(map(check_record, record_tuples)) |
|
3082 |
- |
|
3083 |
- return message_emitted |
|
3084 |
- |
|
3085 |
- |
|
3086 |
-# No need to assert debug messages as of yet. |
|
3087 |
-info_emitted = message_emitted_factory(logging.INFO) |
|
3088 |
-warning_emitted = message_emitted_factory(logging.WARNING) |
|
3089 |
-deprecation_warning_emitted = message_emitted_factory( |
|
3090 |
- logging.WARNING, logger_name=f"{cli.PROG_NAME}.deprecation" |
|
3091 |
-) |
|
3092 |
-deprecation_info_emitted = message_emitted_factory( |
|
3093 |
- logging.INFO, logger_name=f"{cli.PROG_NAME}.deprecation" |
|
3094 |
-) |
|
3095 |
-error_emitted = message_emitted_factory(logging.ERROR) |
|
3096 |
- |
|
3097 |
- |
|
3098 |
-class Parametrize(types.SimpleNamespace): |
|
3099 |
- VAULT_CONFIG_FORMATS_DATA = pytest.mark.parametrize( |
|
3100 |
- ["config", "format", "config_data"], |
|
3101 |
- [ |
|
3102 |
- pytest.param( |
|
3103 |
- VAULT_V02_CONFIG, |
|
3104 |
- "v0.2", |
|
3105 |
- VAULT_V02_CONFIG_DATA, |
|
3106 |
- id="0.2", |
|
3107 |
- ), |
|
3108 |
- pytest.param( |
|
3109 |
- VAULT_V03_CONFIG, |
|
3110 |
- "v0.3", |
|
3111 |
- VAULT_V03_CONFIG_DATA, |
|
3112 |
- id="0.3", |
|
3113 |
- ), |
|
3114 |
- pytest.param( |
|
3115 |
- VAULT_STOREROOM_CONFIG_ZIPPED, |
|
3116 |
- "storeroom", |
|
3117 |
- VAULT_STOREROOM_CONFIG_DATA, |
|
3118 |
- id="storeroom", |
|
3119 |
- ), |
|
3120 |
- ], |
|
3121 |
- ) |
... | ... |
@@ -19,7 +19,9 @@ import hypothesis |
19 | 19 |
import packaging.version |
20 | 20 |
import pytest |
21 | 21 |
|
22 |
-import tests |
|
22 |
+import tests.data |
|
23 |
+import tests.data.callables |
|
24 |
+import tests.machinery |
|
23 | 25 |
from derivepassphrase import _types, ssh_agent |
24 | 26 |
|
25 | 27 |
if TYPE_CHECKING: |
... | ... |
@@ -281,28 +283,28 @@ def spawn_noop( # pragma: no cover [unused] |
281 | 283 |
"""Placeholder function. Does nothing.""" |
282 | 284 |
|
283 | 285 |
|
284 |
-spawn_handlers: dict[str, tuple[str, SpawnFunc, tests.KnownSSHAgent]] = { |
|
286 |
+spawn_handlers: dict[str, tuple[str, SpawnFunc, tests.data.KnownSSHAgent]] = { |
|
285 | 287 |
"pageant": ( |
286 | 288 |
"pageant", |
287 | 289 |
spawn_pageant_on_posix, |
288 |
- tests.KnownSSHAgent.Pageant, |
|
290 |
+ tests.data.KnownSSHAgent.Pageant, |
|
289 | 291 |
), |
290 | 292 |
"ssh-agent": ( |
291 | 293 |
"ssh-agent", |
292 | 294 |
spawn_openssh_agent_on_posix, |
293 |
- tests.KnownSSHAgent.OpenSSHAgent, |
|
295 |
+ tests.data.KnownSSHAgent.OpenSSHAgent, |
|
294 | 296 |
), |
295 | 297 |
"stub_agent": ( |
296 | 298 |
"stub_agent", |
297 | 299 |
spawn_noop, |
298 |
- tests.KnownSSHAgent.StubbedSSHAgent, |
|
300 |
+ tests.data.KnownSSHAgent.StubbedSSHAgent, |
|
299 | 301 |
), |
300 | 302 |
"stub_agent_with_extensions": ( |
301 | 303 |
"stub_agent_with_extensions", |
302 | 304 |
spawn_noop, |
303 |
- tests.KnownSSHAgent.StubbedSSHAgent, |
|
305 |
+ tests.data.KnownSSHAgent.StubbedSSHAgent, |
|
304 | 306 |
), |
305 |
- "(system)": ("(system)", spawn_noop, tests.KnownSSHAgent.UNKNOWN), |
|
307 |
+ "(system)": ("(system)", spawn_noop, tests.data.KnownSSHAgent.UNKNOWN), |
|
306 | 308 |
} |
307 | 309 |
""" |
308 | 310 |
The standard registry of agent spawning functions. |
... | ... |
@@ -339,8 +341,8 @@ class CannotSpawnError(RuntimeError): |
339 | 341 |
def spawn_named_agent( |
340 | 342 |
exec_name: str, |
341 | 343 |
spawn_func: SpawnFunc, |
342 |
- agent_type: tests.KnownSSHAgent, |
|
343 |
-) -> Iterator[tests.SpawnedSSHAgentInfo]: # pragma: no cover [external] |
|
344 |
+ agent_type: tests.data.KnownSSHAgent, |
|
345 |
+) -> Iterator[tests.data.SpawnedSSHAgentInfo]: # pragma: no cover [external] |
|
344 | 346 |
"""Spawn the named SSH agent and check that it is operational. |
345 | 347 |
|
346 | 348 |
Using the correct agent-specific spawn function from the |
... | ... |
@@ -396,7 +398,7 @@ def spawn_named_agent( |
396 | 398 |
with exit_stack: |
397 | 399 |
if ( |
398 | 400 |
spawn_func is spawn_noop |
399 |
- and agent_type == tests.KnownSSHAgent.StubbedSSHAgent |
|
401 |
+ and agent_type == tests.data.KnownSSHAgent.StubbedSSHAgent |
|
400 | 402 |
): |
401 | 403 |
ssh_auth_sock = None |
402 | 404 |
elif spawn_func is spawn_noop: |
... | ... |
@@ -412,7 +414,7 @@ def spawn_named_agent( |
412 | 414 |
assert proc.stdout is not None |
413 | 415 |
ssh_auth_sock_line = proc.stdout.readline() |
414 | 416 |
try: |
415 |
- ssh_auth_sock = tests.parse_sh_export_line( |
|
417 |
+ ssh_auth_sock = tests.data.callables.parse_sh_export_line( |
|
416 | 418 |
ssh_auth_sock_line, env_name="SSH_AUTH_SOCK" |
417 | 419 |
) |
418 | 420 |
except ValueError: # pragma: no cover [external] |
... | ... |
@@ -433,7 +435,7 @@ def spawn_named_agent( |
433 | 435 |
) |
434 | 436 |
else: |
435 | 437 |
monkeypatch.setenv( |
436 |
- "SSH_AUTH_SOCK", tests.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
438 |
+ "SSH_AUTH_SOCK", tests.machinery.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
437 | 439 |
) |
438 | 440 |
monkeypatch.setattr( |
439 | 441 |
ssh_agent.SSHAgentClient, |
... | ... |
@@ -444,9 +446,9 @@ def spawn_named_agent( |
444 | 446 |
) |
445 | 447 |
client = exit_stack.enter_context( |
446 | 448 |
ssh_agent.SSHAgentClient.ensure_agent_subcontext( |
447 |
- tests.StubbedSSHAgentSocketWithAddressAndDeterministicDSA() |
|
449 |
+ tests.machinery.StubbedSSHAgentSocketWithAddressAndDeterministicDSA() |
|
448 | 450 |
if exec_name == "stub_agent_with_extensions" |
449 |
- else tests.StubbedSSHAgentSocketWithAddress() |
|
451 |
+ else tests.machinery.StubbedSSHAgentSocketWithAddress() |
|
450 | 452 |
) |
451 | 453 |
) |
452 | 454 |
# We sanity-test the connected SSH agent if it is not one of our |
... | ... |
@@ -460,7 +462,7 @@ def spawn_named_agent( |
460 | 462 |
# agent is not one of our test agents, and if the check fails, |
461 | 463 |
# skip this agent. |
462 | 464 |
if ( |
463 |
- agent_type != tests.KnownSSHAgent.StubbedSSHAgent |
|
465 |
+ agent_type != tests.data.KnownSSHAgent.StubbedSSHAgent |
|
464 | 466 |
): # pragma: no cover [external] |
465 | 467 |
try: |
466 | 468 |
client.list_keys() # sanity test |
... | ... |
@@ -471,7 +473,7 @@ def spawn_named_agent( |
471 | 473 |
) as exc: # pragma: no cover [failsafe] |
472 | 474 |
msg = f'agent failed the "list keys" sanity test: {exc!r}' |
473 | 475 |
raise CannotSpawnError(msg) from exc |
474 |
- yield tests.SpawnedSSHAgentInfo( |
|
476 |
+ yield tests.data.SpawnedSSHAgentInfo( |
|
475 | 477 |
agent_type, client, spawn_func is not spawn_noop |
476 | 478 |
) |
477 | 479 |
assert os.environ.get("SSH_AUTH_SOCK", None) == startup_ssh_auth_sock, ( |
... | ... |
@@ -480,7 +482,7 @@ def spawn_named_agent( |
480 | 482 |
|
481 | 483 |
|
482 | 484 |
def is_agent_permitted( |
483 |
- agent_type: tests.KnownSSHAgent, |
|
485 |
+ agent_type: tests.data.KnownSSHAgent, |
|
484 | 486 |
) -> bool: # pragma: no cover [external] |
485 | 487 |
"""May the given SSH agent be spawned by the test harness? |
486 | 488 |
|
... | ... |
@@ -498,11 +500,11 @@ def is_agent_permitted( |
498 | 500 |
""" |
499 | 501 |
if not os.environ.get("PERMITTED_SSH_AGENTS"): |
500 | 502 |
return True |
501 |
- permitted_agents = {tests.KnownSSHAgent.StubbedSSHAgent} |
|
503 |
+ permitted_agents = {tests.data.KnownSSHAgent.StubbedSSHAgent} |
|
502 | 504 |
permitted_agents.update({ |
503 |
- tests.KnownSSHAgent(x) |
|
505 |
+ tests.data.KnownSSHAgent(x) |
|
504 | 506 |
for x in os.environ["PERMITTED_SSH_AGENTS"].split(",") |
505 |
- if x in tests.KnownSSHAgent.__members__ |
|
507 |
+ if x in tests.data.KnownSSHAgent.__members__ |
|
506 | 508 |
}) |
507 | 509 |
return agent_type in permitted_agents |
508 | 510 |
|
... | ... |
@@ -532,7 +534,7 @@ for key, handler in spawn_handlers.items(): |
532 | 534 |
|
533 | 535 |
@pytest.fixture |
534 | 536 |
def running_ssh_agent( # pragma: no cover [external] |
535 |
-) -> Iterator[tests.RunningSSHAgentInfo]: |
|
537 |
+) -> Iterator[tests.data.RunningSSHAgentInfo]: |
|
536 | 538 |
"""Ensure a running SSH agent, if possible, as a pytest fixture. |
537 | 539 |
|
538 | 540 |
Check for a running SSH agent, or spawn a new one if possible. We |
... | ... |
@@ -555,10 +557,10 @@ def running_ssh_agent( # pragma: no cover [external] |
555 | 557 |
""" |
556 | 558 |
|
557 | 559 |
def prepare_environment( |
558 |
- agent_type: tests.KnownSSHAgent, |
|
559 |
- ) -> Iterator[tests.RunningSSHAgentInfo]: |
|
560 |
+ agent_type: tests.data.KnownSSHAgent, |
|
561 |
+ ) -> Iterator[tests.data.RunningSSHAgentInfo]: |
|
560 | 562 |
with pytest.MonkeyPatch.context() as monkeypatch: |
561 |
- if agent_type == tests.KnownSSHAgent.StubbedSSHAgent: |
|
563 |
+ if agent_type == tests.data.KnownSSHAgent.StubbedSSHAgent: |
|
562 | 564 |
monkeypatch.setattr( |
563 | 565 |
ssh_agent.SSHAgentClient, |
564 | 566 |
"SOCKET_PROVIDERS", |
... | ... |
@@ -566,14 +568,14 @@ def running_ssh_agent( # pragma: no cover [external] |
566 | 568 |
) |
567 | 569 |
monkeypatch.setenv( |
568 | 570 |
"SSH_AUTH_SOCK", |
569 |
- tests.StubbedSSHAgentSocketWithAddress.ADDRESS, |
|
571 |
+ tests.machinery.StubbedSSHAgentSocketWithAddress.ADDRESS, |
|
570 | 572 |
) |
571 |
- yield tests.RunningSSHAgentInfo( |
|
572 |
- tests.StubbedSSHAgentSocketWithAddress, |
|
573 |
- tests.KnownSSHAgent.StubbedSSHAgent, |
|
573 |
+ yield tests.data.RunningSSHAgentInfo( |
|
574 |
+ tests.machinery.StubbedSSHAgentSocketWithAddress, |
|
575 |
+ tests.data.KnownSSHAgent.StubbedSSHAgent, |
|
574 | 576 |
) |
575 | 577 |
else: |
576 |
- yield tests.RunningSSHAgentInfo( |
|
578 |
+ yield tests.data.RunningSSHAgentInfo( |
|
577 | 579 |
os.environ["SSH_AUTH_SOCK"], |
578 | 580 |
agent_type, |
579 | 581 |
) |
... | ... |
@@ -607,7 +609,7 @@ def running_ssh_agent( # pragma: no cover [external] |
607 | 609 |
@pytest.fixture(params=spawn_handlers_params) |
608 | 610 |
def spawn_ssh_agent( |
609 | 611 |
request: pytest.FixtureRequest, |
610 |
-) -> Iterator[tests.SpawnedSSHAgentInfo]: # pragma: no cover [external] |
|
612 |
+) -> Iterator[tests.data.SpawnedSSHAgentInfo]: # pragma: no cover [external] |
|
611 | 613 |
"""Spawn an isolated SSH agent, if possible, as a pytest fixture. |
612 | 614 |
|
613 | 615 |
Spawn a new SSH agent isolated from other SSH use by other |
... | ... |
@@ -648,7 +650,7 @@ def spawn_ssh_agent( |
648 | 650 |
|
649 | 651 |
@pytest.fixture |
650 | 652 |
def ssh_agent_client_with_test_keys_loaded( # noqa: C901 |
651 |
- spawn_ssh_agent: tests.SpawnedSSHAgentInfo, |
|
653 |
+ spawn_ssh_agent: tests.data.SpawnedSSHAgentInfo, |
|
652 | 654 |
) -> Iterator[ssh_agent.SSHAgentClient]: |
653 | 655 |
"""Provide an SSH agent with loaded test keys, as a pytest fixture. |
654 | 656 |
|
... | ... |
@@ -711,7 +713,7 @@ def ssh_agent_client_with_test_keys_loaded( # noqa: C901 |
711 | 713 |
return (return_code, bytes(payload) + lifetime_constraint) |
712 | 714 |
|
713 | 715 |
try: |
714 |
- for key_type, key_struct in tests.ALL_KEYS.items(): |
|
716 |
+ for key_type, key_struct in tests.data.ALL_KEYS.items(): |
|
715 | 717 |
private_key_data = key_struct.private_key_blob |
716 | 718 |
if private_key_data is None: # pragma: no cover [failsafe] |
717 | 719 |
continue |
... | ... |
@@ -741,11 +743,11 @@ def ssh_agent_client_with_test_keys_loaded( # noqa: C901 |
741 | 743 |
current_loaded_keys = frozenset({ |
742 | 744 |
pair.key for pair in client.list_keys() |
743 | 745 |
}) |
744 |
- if agent_type == tests.KnownSSHAgent.Pageant and ( |
|
746 |
+ if agent_type == tests.data.KnownSSHAgent.Pageant and ( |
|
745 | 747 |
key_struct.public_key_data in current_loaded_keys |
746 | 748 |
): |
747 | 749 |
pass |
748 |
- elif agent_type == tests.KnownSSHAgent.Pageant and ( |
|
750 |
+ elif agent_type == tests.data.KnownSSHAgent.Pageant and ( |
|
749 | 751 |
not isolated |
750 | 752 |
): |
751 | 753 |
request_code, payload = prepare_payload( |
... | ... |
@@ -768,7 +770,7 @@ def ssh_agent_client_with_test_keys_loaded( # noqa: C901 |
768 | 770 |
successfully_loaded_keys.add(key_type) |
769 | 771 |
yield client |
770 | 772 |
finally: |
771 |
- for key_type, key_struct in tests.ALL_KEYS.items(): |
|
773 |
+ for key_type, key_struct in tests.data.ALL_KEYS.items(): |
|
772 | 774 |
if not isolated and ( |
773 | 775 |
key_type in successfully_loaded_keys |
774 | 776 |
): # pragma: no cover [external] |
... | ... |
@@ -0,0 +1,1761 @@ |
1 |
+# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: Zlib |
|
4 |
+ |
|
5 |
+"""Testing data for `derivepassphrase`. |
|
6 |
+ |
|
7 |
+This is all the standalone, non-test-system-specific data set aside |
|
8 |
+specifically to test `derivepassphrase`; this includes types, `vault` |
|
9 |
+test configurations, test SSH keys and the like. |
|
10 |
+ |
|
11 |
+This module only contains data, and some "mostly trivial" code such as |
|
12 |
+accessors or suitability checkers; all code more complicated than that |
|
13 |
+is in the `tests.data.callables` module (if independent of, and sensible |
|
14 |
+to call outside, the testing environment) or the `tests.machinery` |
|
15 |
+module (otherwise). All data that specifically relates to the testing |
|
16 |
+system itself is also in the `tests.machinery` module, not here. |
|
17 |
+ |
|
18 |
+""" |
|
19 |
+ |
|
20 |
+from __future__ import annotations |
|
21 |
+ |
|
22 |
+import base64 |
|
23 |
+import enum |
|
24 |
+from typing import TYPE_CHECKING |
|
25 |
+ |
|
26 |
+from typing_extensions import NamedTuple |
|
27 |
+ |
|
28 |
+from derivepassphrase import _types, ssh_agent, vault |
|
29 |
+from derivepassphrase.ssh_agent import socketprovider |
|
30 |
+ |
|
31 |
+__all__ = () |
|
32 |
+ |
|
33 |
+if TYPE_CHECKING: |
|
34 |
+ from collections.abc import Mapping |
|
35 |
+ |
|
36 |
+ from typing_extensions import Any |
|
37 |
+ |
|
38 |
+ |
|
39 |
+# Types |
|
40 |
+# ===== |
|
41 |
+ |
|
42 |
+# SSH test keys and agent info |
|
43 |
+# ---------------------------- |
|
44 |
+ |
|
45 |
+ |
|
46 |
+class SSHTestKeyDeterministicSignatureClass(str, enum.Enum): |
|
47 |
+ """The class of a deterministic signature from an SSH test key. |
|
48 |
+ |
|
49 |
+ Attributes: |
|
50 |
+ SPEC: |
|
51 |
+ A deterministic signature directly implied by the |
|
52 |
+ specification of the signature algorithm. |
|
53 |
+ RFC_6979: |
|
54 |
+ A deterministic signature as specified by RFC 6979. Only |
|
55 |
+ used with DSA and ECDSA keys (that aren't also EdDSA keys). |
|
56 |
+ Pageant_068_080: |
|
57 |
+ A deterministic signature as specified by Pageant 0.68. |
|
58 |
+ Only used with DSA and ECDSA keys (that aren't also EdDSA |
|
59 |
+ keys), and only used with Pageant from 0.68 up to and |
|
60 |
+ including 0.80. |
|
61 |
+ |
|
62 |
+ Usage of this signature class together with an ECDSA NIST |
|
63 |
+ P-521 key [turned out to leak enough information per |
|
64 |
+ signature to quickly compromise the entire private key |
|
65 |
+ (CVE-2024-31497)][PUTTY_CVE_2024_31497], so newer Pageant |
|
66 |
+ versions abandon this signature class in favor of RFC 6979. |
|
67 |
+ |
|
68 |
+ [PUTTY_CVE_2024_31497]: https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/vuln-p521-bias.html |
|
69 |
+ |
|
70 |
+ """ |
|
71 |
+ |
|
72 |
+ SPEC = enum.auto() |
|
73 |
+ """""" |
|
74 |
+ RFC_6979 = enum.auto() |
|
75 |
+ """""" |
|
76 |
+ Pageant_068_080 = enum.auto() |
|
77 |
+ """""" |
|
78 |
+ |
|
79 |
+ |
|
80 |
+class SSHTestKeyDeterministicSignature(NamedTuple): |
|
81 |
+ """An SSH test key deterministic signature. |
|
82 |
+ |
|
83 |
+ Attributes: |
|
84 |
+ signature: |
|
85 |
+ The binary signature of the [vault UUID][vault.Vault.UUID] |
|
86 |
+ under this signature class. |
|
87 |
+ derived_passphrase: |
|
88 |
+ The equivalent master passphrase derived from this |
|
89 |
+ signature. |
|
90 |
+ signature_class: |
|
91 |
+ The [signature |
|
92 |
+ class][SSHTestKeyDeterministicSignatureClass]. |
|
93 |
+ |
|
94 |
+ """ |
|
95 |
+ |
|
96 |
+ signature: bytes |
|
97 |
+ """""" |
|
98 |
+ derived_passphrase: bytes |
|
99 |
+ """""" |
|
100 |
+ signature_class: SSHTestKeyDeterministicSignatureClass = ( |
|
101 |
+ SSHTestKeyDeterministicSignatureClass.SPEC |
|
102 |
+ ) |
|
103 |
+ """""" |
|
104 |
+ |
|
105 |
+ |
|
106 |
+class SSHTestKey(NamedTuple): |
|
107 |
+ """An SSH test key. |
|
108 |
+ |
|
109 |
+ Attributes: |
|
110 |
+ public_key: |
|
111 |
+ The SSH public key string, as used e.g. by OpenSSH's |
|
112 |
+ `authorized_keys` file. Includes a comment. |
|
113 |
+ public_key_data: |
|
114 |
+ The SSH protocol wire format of the public key. |
|
115 |
+ private_key: |
|
116 |
+ A base64 encoded representation of the private key, in |
|
117 |
+ OpenSSH's v1 private key format. |
|
118 |
+ private_key_blob: |
|
119 |
+ The SSH protocol wire format of the private key. |
|
120 |
+ expected_signatures: |
|
121 |
+ A mapping of deterministic signature classes to the |
|
122 |
+ expected, deterministic signature (of that class) of the |
|
123 |
+ vault UUID for this key, together with the respective |
|
124 |
+ "equivalent master passphrase" derived from this signature. |
|
125 |
+ |
|
126 |
+ """ |
|
127 |
+ |
|
128 |
+ public_key: bytes |
|
129 |
+ """""" |
|
130 |
+ public_key_data: bytes |
|
131 |
+ """""" |
|
132 |
+ private_key: bytes |
|
133 |
+ """""" |
|
134 |
+ private_key_blob: bytes |
|
135 |
+ """""" |
|
136 |
+ expected_signatures: Mapping[ |
|
137 |
+ SSHTestKeyDeterministicSignatureClass, SSHTestKeyDeterministicSignature |
|
138 |
+ ] |
|
139 |
+ """""" |
|
140 |
+ |
|
141 |
+ def is_suitable( |
|
142 |
+ self, |
|
143 |
+ *, |
|
144 |
+ client: ssh_agent.SSHAgentClient | None = None, |
|
145 |
+ ) -> bool: |
|
146 |
+ """Return if this key is suitable for use with vault. |
|
147 |
+ |
|
148 |
+ Args: |
|
149 |
+ client: |
|
150 |
+ An optional SSH agent client to check for additional |
|
151 |
+ deterministic key types. If not given, assume no such |
|
152 |
+ types. |
|
153 |
+ |
|
154 |
+ """ |
|
155 |
+ return vault.Vault.is_suitable_ssh_key( |
|
156 |
+ self.public_key_data, client=client |
|
157 |
+ ) |
|
158 |
+ |
|
159 |
+ |
|
160 |
+class KnownSSHAgent(str, enum.Enum): |
|
161 |
+ """Known SSH agents. |
|
162 |
+ |
|
163 |
+ Attributes: |
|
164 |
+ UNKNOWN (str): |
|
165 |
+ Not a known agent, or not known statically. |
|
166 |
+ Pageant (str): |
|
167 |
+ The agent from Simon Tatham's PuTTY suite. |
|
168 |
+ OpenSSHAgent (str): |
|
169 |
+ The agent from OpenBSD's OpenSSH suite. |
|
170 |
+ StubbedSSHAgent (str): |
|
171 |
+ The stubbed, fake agent pseudo-socket defined in this test |
|
172 |
+ suite. |
|
173 |
+ |
|
174 |
+ """ |
|
175 |
+ |
|
176 |
+ UNKNOWN = "(unknown)" |
|
177 |
+ """""" |
|
178 |
+ Pageant = "Pageant" |
|
179 |
+ """""" |
|
180 |
+ OpenSSHAgent = "OpenSSHAgent" |
|
181 |
+ """""" |
|
182 |
+ StubbedSSHAgent = "StubbedSSHAgent" |
|
183 |
+ """""" |
|
184 |
+ |
|
185 |
+ |
|
186 |
+class SpawnedSSHAgentInfo(NamedTuple): |
|
187 |
+ """Info about a spawned SSH agent, as provided by some fixtures. |
|
188 |
+ |
|
189 |
+ Differs from [`RunningSSHAgentInfo`][] in that this info object |
|
190 |
+ already provides a functional client connected to the agent, but not |
|
191 |
+ the address. |
|
192 |
+ |
|
193 |
+ Attributes: |
|
194 |
+ agent_type: |
|
195 |
+ The agent's type. |
|
196 |
+ client: |
|
197 |
+ An SSH agent client connected to this agent. |
|
198 |
+ isolated: |
|
199 |
+ Whether this agent was spawned specifically for this test |
|
200 |
+ suite, with attempts to isolate it from the user. If false, |
|
201 |
+ then the user may be interacting with the agent externally, |
|
202 |
+ meaning e.g. keys other than the test keys may be visible in |
|
203 |
+ this agent. |
|
204 |
+ |
|
205 |
+ """ |
|
206 |
+ |
|
207 |
+ agent_type: KnownSSHAgent |
|
208 |
+ """""" |
|
209 |
+ client: ssh_agent.SSHAgentClient |
|
210 |
+ """""" |
|
211 |
+ isolated: bool |
|
212 |
+ """""" |
|
213 |
+ |
|
214 |
+ |
|
215 |
+class RunningSSHAgentInfo(NamedTuple): |
|
216 |
+ """Info about a running SSH agent, as provided by some fixtures. |
|
217 |
+ |
|
218 |
+ Differs from [`SpawnedSSHAgentInfo`][] in that this info object |
|
219 |
+ provides only an address of the agent, not a functional client |
|
220 |
+ already connected to it. The running SSH agent may or may not be |
|
221 |
+ isolated. |
|
222 |
+ |
|
223 |
+ Attributes: |
|
224 |
+ socket: |
|
225 |
+ A socket address to connect to the agent. |
|
226 |
+ agent_type: |
|
227 |
+ The agent's type. |
|
228 |
+ |
|
229 |
+ """ |
|
230 |
+ |
|
231 |
+ socket: str | type[_types.SSHAgentSocket] |
|
232 |
+ """""" |
|
233 |
+ agent_type: KnownSSHAgent |
|
234 |
+ """""" |
|
235 |
+ |
|
236 |
+ def require_external_address(self) -> str: # pragma: no cover |
|
237 |
+ if not isinstance(self.socket, str): |
|
238 |
+ import pytest # noqa: PLC0415 |
|
239 |
+ |
|
240 |
+ pytest.skip( |
|
241 |
+ reason="This test requires a real, externally resolvable " |
|
242 |
+ "address for the SSH agent socket." |
|
243 |
+ ) |
|
244 |
+ return self.socket |
|
245 |
+ |
|
246 |
+ |
|
247 |
+# Vault configurations |
|
248 |
+# ==================== |
|
249 |
+ |
|
250 |
+ |
|
251 |
+class ValidationSettings(NamedTuple): |
|
252 |
+ """Validation settings for [`VaultTestConfig`][]s. |
|
253 |
+ |
|
254 |
+ Attributes: |
|
255 |
+ allow_unknown_settings: |
|
256 |
+ See [`_types.validate_vault_config`][]. |
|
257 |
+ |
|
258 |
+ """ |
|
259 |
+ |
|
260 |
+ allow_unknown_settings: bool |
|
261 |
+ """""" |
|
262 |
+ |
|
263 |
+ |
|
264 |
+class VaultTestConfig(NamedTuple): |
|
265 |
+ """A (not necessarily valid) sample vault config, plus metadata. |
|
266 |
+ |
|
267 |
+ Attributes: |
|
268 |
+ config: |
|
269 |
+ The actual configuration object. Usually a [`dict`][]. |
|
270 |
+ comment: |
|
271 |
+ An explanatory comment for what is wrong with this config, |
|
272 |
+ or empty if the config is valid. This is intended as |
|
273 |
+ a debugging message to be shown to the user (e.g. when an |
|
274 |
+ assertion fails), not as an error message to |
|
275 |
+ programmatically match against. |
|
276 |
+ validation_settings: |
|
277 |
+ See [`_types.validate_vault_config`][]. |
|
278 |
+ |
|
279 |
+ """ |
|
280 |
+ |
|
281 |
+ config: Any |
|
282 |
+ """""" |
|
283 |
+ comment: str |
|
284 |
+ """""" |
|
285 |
+ validation_settings: ValidationSettings | None |
|
286 |
+ """""" |
|
287 |
+ |
|
288 |
+ |
|
289 |
+def is_valid_test_config(conf: VaultTestConfig, /) -> bool: |
|
290 |
+ """Return true if the test config is valid. |
|
291 |
+ |
|
292 |
+ Args: |
|
293 |
+ conf: The test config to check. |
|
294 |
+ |
|
295 |
+ """ |
|
296 |
+ return not conf.comment and conf.validation_settings in { |
|
297 |
+ None, |
|
298 |
+ (True,), |
|
299 |
+ } |
|
300 |
+ |
|
301 |
+ |
|
302 |
+def is_smudgable_vault_test_config(conf: VaultTestConfig) -> bool: |
|
303 |
+ """Check whether this vault test config can be effectively smudged. |
|
304 |
+ |
|
305 |
+ A "smudged" test config is one where falsy values (in the JavaScript |
|
306 |
+ sense) can be replaced by other falsy values without changing the |
|
307 |
+ meaning of the config. |
|
308 |
+ |
|
309 |
+ Args: |
|
310 |
+ conf: A test config to check. |
|
311 |
+ |
|
312 |
+ Returns: |
|
313 |
+ True if the test config can be smudged, False otherwise. |
|
314 |
+ |
|
315 |
+ """ |
|
316 |
+ config = conf.config |
|
317 |
+ return bool( |
|
318 |
+ isinstance(config, dict) |
|
319 |
+ and ("global" not in config or isinstance(config["global"], dict)) |
|
320 |
+ and ("services" in config and isinstance(config["services"], dict)) |
|
321 |
+ and all(isinstance(x, dict) for x in config["services"].values()) |
|
322 |
+ and (config["services"] or config.get("global")) |
|
323 |
+ ) |
|
324 |
+ |
|
325 |
+ |
|
326 |
+def _test_config_ids(val: VaultTestConfig) -> Any: # pragma: no cover |
|
327 |
+ """pytest id function for VaultTestConfig objects.""" |
|
328 |
+ assert isinstance(val, VaultTestConfig) |
|
329 |
+ return val[1] or (val[0], val[1], val[2]) |
|
330 |
+ |
|
331 |
+# Data objects |
|
332 |
+# ============ |
|
333 |
+ |
|
334 |
+ |
|
335 |
+# SSH agent socket provider data |
|
336 |
+# ------------------------------ |
|
337 |
+ |
|
338 |
+ |
|
339 |
+posix_entry = _types.SSHAgentSocketProviderEntry( |
|
340 |
+ socketprovider.SocketProvider.resolve("posix"), "posix", () |
|
341 |
+) |
|
342 |
+""" |
|
343 |
+The standard [`_types.SSHAgentSocketProviderEntry`][] for the UNIX |
|
344 |
+domain socket handler on POSIX systems. |
|
345 |
+""" |
|
346 |
+ |
|
347 |
+the_annoying_os_entry = _types.SSHAgentSocketProviderEntry( |
|
348 |
+ socketprovider.SocketProvider.resolve("the_annoying_os"), |
|
349 |
+ "the_annoying_os", |
|
350 |
+ (), |
|
351 |
+) |
|
352 |
+""" |
|
353 |
+The standard [`_types.SSHAgentSocketProviderEntry`][] for the named pipe |
|
354 |
+handler on The Annoying Operating System. |
|
355 |
+""" |
|
356 |
+ |
|
357 |
+faulty_entry_callable = _types.SSHAgentSocketProviderEntry( |
|
358 |
+ (), # type: ignore[arg-type] |
|
359 |
+ "tuple", |
|
360 |
+ (), |
|
361 |
+) |
|
362 |
+""" |
|
363 |
+A faulty [`_types.SSHAgentSocketProviderEntry`][]: the indicated handler |
|
364 |
+is not a callable. |
|
365 |
+""" |
|
366 |
+ |
|
367 |
+faulty_entry_name_exists = _types.SSHAgentSocketProviderEntry( |
|
368 |
+ socketprovider.SocketProvider.resolve("the_annoying_os"), "posix", () |
|
369 |
+) |
|
370 |
+""" |
|
371 |
+A faulty [`_types.SSHAgentSocketProviderEntry`][]: the indicated handler |
|
372 |
+is already registered with a different callable. |
|
373 |
+""" |
|
374 |
+ |
|
375 |
+faulty_entry_alias_exists = _types.SSHAgentSocketProviderEntry( |
|
376 |
+ socketprovider.SocketProvider.resolve("posix"), |
|
377 |
+ "posix", |
|
378 |
+ ("unix_domain", "the_annoying_os"), |
|
379 |
+) |
|
380 |
+""" |
|
381 |
+A faulty [`_types.SSHAgentSocketProviderEntry`][]: the alias is already |
|
382 |
+registered with a different callable. |
|
383 |
+""" |
|
384 |
+ |
|
385 |
+# SSH test keys |
|
386 |
+# ------------- |
|
387 |
+ |
|
388 |
+ |
|
389 |
+ALL_KEYS: Mapping[str, SSHTestKey] = { |
|
390 |
+ "ed25519": SSHTestKey( |
|
391 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
392 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW |
|
393 |
+QyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdgAAAKDweO7H8Hju |
|
394 |
+xwAAAAtzc2gtZWQyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdg |
|
395 |
+AAAEAbM/A869nkWZbe2tp3Dm/L6gitvmpH/aRZt8sBII3ExYF4gWgm1gJIXw//Mkhv5MEw |
|
396 |
+idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC |
|
397 |
+-----END OPENSSH PRIVATE KEY----- |
|
398 |
+""", |
|
399 |
+ private_key_blob=bytes.fromhex(""" |
|
400 |
+ 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
401 |
+ 00 00 00 20 |
|
402 |
+ 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
403 |
+ 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
404 |
+ 00 00 00 40 |
|
405 |
+ 1b 33 f0 3c eb d9 e4 59 96 de da da 77 0e 6f cb |
|
406 |
+ ea 08 ad be 6a 47 fd a4 59 b7 cb 01 20 8d c4 c5 |
|
407 |
+ 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
408 |
+ 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
409 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 74 |
|
410 |
+ 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
411 |
+"""), |
|
412 |
+ public_key=rb"""ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIF4gWgm1gJIXw//Mkhv5MEwidwcakUGCekJD/vCEml2 test key without passphrase |
|
413 |
+""", |
|
414 |
+ public_key_data=bytes.fromhex(""" |
|
415 |
+ 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
416 |
+ 00 00 00 20 |
|
417 |
+ 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1 |
|
418 |
+ 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
419 |
+"""), |
|
420 |
+ expected_signatures={ |
|
421 |
+ SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
422 |
+ signature=bytes.fromhex(""" |
|
423 |
+ 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
424 |
+ 00 00 00 40 |
|
425 |
+ f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86 |
|
426 |
+ 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd |
|
427 |
+ 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c |
|
428 |
+ 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02 |
|
429 |
+"""), |
|
430 |
+ derived_passphrase=rb"""8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==""", |
|
431 |
+ ), |
|
432 |
+ }, |
|
433 |
+ ), |
|
434 |
+ # Currently only supported by PuTTY (which is deficient in other |
|
435 |
+ # niceties of the SSH agent and the agent's client). |
|
436 |
+ "ed448": SSHTestKey( |
|
437 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
438 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz |
|
439 |
+c2gtZWQ0NDgAAAA54vZy009Wu8wExjvEb3hqtLz1GO/+d5vmGUbErWQ4AUO9mYLT |
|
440 |
+zHJHc2m4s+yWzP29Cc3EcxizLG8AAAAA8BdhfCcXYXwnAAAACXNzaC1lZDQ0OAAA |
|
441 |
+ADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM |
|
442 |
+/b0JzcRzGLMsbwAAAAByM7GIMRvWJB3YD6SIpAF2uudX4ozZe0X917wPwiBrs373 |
|
443 |
+9TM1n94Nib6hrxGNmCk2iBQDe2KALPgA4vZy009Wu8wExjvEb3hqtLz1GO/+d5vm |
|
444 |
+GUbErWQ4AUO9mYLTzHJHc2m4s+yWzP29Cc3EcxizLG8AAAAAG3Rlc3Qga2V5IHdp |
|
445 |
+dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ== |
|
446 |
+-----END OPENSSH PRIVATE KEY----- |
|
447 |
+""", |
|
448 |
+ private_key_blob=bytes.fromhex(""" |
|
449 |
+ 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
450 |
+ 00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04 |
|
451 |
+ c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
452 |
+ 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
453 |
+ b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
454 |
+ 00 00 00 72 33 b1 |
|
455 |
+ 88 31 1b d6 24 1d d8 0f a4 88 a4 01 76 ba e7 57 |
|
456 |
+ e2 8c d9 7b 45 fd d7 bc 0f c2 20 6b b3 7e f7 f5 |
|
457 |
+ 33 35 9f de 0d 89 be a1 af 11 8d 98 29 36 88 14 |
|
458 |
+ 03 7b 62 80 2c f8 00 e2 f6 72 d3 4f 56 bb cc 04 |
|
459 |
+ c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
460 |
+ 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
461 |
+ b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
462 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
463 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
464 |
+"""), |
|
465 |
+ public_key=rb"""ssh-ed448 AAAACXNzaC1lZDQ0OAAAADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM/b0JzcRzGLMsbwA= test key without passphrase |
|
466 |
+""", |
|
467 |
+ public_key_data=bytes.fromhex(""" |
|
468 |
+ 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
469 |
+ 00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04 |
|
470 |
+ c6 3b c4 6f 78 6a b4 bc f5 18 ef fe 77 9b e6 19 |
|
471 |
+ 46 c4 ad 64 38 01 43 bd 99 82 d3 cc 72 47 73 69 |
|
472 |
+ b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f 00 |
|
473 |
+ """), |
|
474 |
+ expected_signatures={ |
|
475 |
+ SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
476 |
+ signature=bytes.fromhex(""" |
|
477 |
+ 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
478 |
+ 00 00 00 72 06 86 |
|
479 |
+ f4 64 a4 a6 ba d9 c3 22 c4 93 49 99 fc 11 de 67 |
|
480 |
+ 97 08 f2 d8 b7 3c 2c 13 e7 c5 1c 1e 92 a6 0e d8 |
|
481 |
+ 2f 6d 81 03 82 00 e3 72 e4 32 6d 72 d2 6d 32 84 |
|
482 |
+ 3f cc a9 1e 57 2c 00 9a b3 99 de 45 da ce 2e d1 |
|
483 |
+ db e5 89 f3 35 be 24 58 90 c6 ca 04 f0 db 88 80 |
|
484 |
+ db bd 77 7c 80 20 7f 3a 48 61 f6 1f ae a9 5e 53 |
|
485 |
+ 7b e0 9d 93 1e ea dc eb b5 cd 56 4c ea 8f 08 00 |
|
486 |
+"""), |
|
487 |
+ derived_passphrase=rb"""Bob0ZKSmutnDIsSTSZn8Ed5nlwjy2Lc8LBPnxRwekqYO2C9tgQOCAONy5DJtctJtMoQ/zKkeVywAmrOZ3kXazi7R2+WJ8zW+JFiQxsoE8NuIgNu9d3yAIH86SGH2H66pXlN74J2THurc67XNVkzqjwgA""", |
|
488 |
+ ), |
|
489 |
+ }, |
|
490 |
+ ), |
|
491 |
+ "rsa": SSHTestKey( |
|
492 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
493 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn |
|
494 |
+NhAAAAAwEAAQAAAYEAsaHu6Xs4cVsuDSNJlMCqoPVgmDgEviI8TfXmHKqX3JkIqI3LsvV7 |
|
495 |
+Ijf8WCdTveEq7CkuZhImtsR52AOEVAoU8mDXDNr+nJ5wUPzf1UIaRjDe0lcXW4SlF01hQs |
|
496 |
+G4wYDuqxshwelraB/L3e0zhD7fjYHF8IbFsqGlFHWEwOtlfhhfbxJsTGguLm4A8/gdEJD5 |
|
497 |
+2rkqDcZpIXCHtJbCzW9aQpWcs/PDw5ylwl/3dB7jfxyfrGz4O3QrzsqhWEsip97mOmwl6q |
|
498 |
+CHbq8V8x9zu89D/H+bG5ijqxhijbjcVUW3lZfw/97gy9J6rG31HNar5H8GycLTFwuCFepD |
|
499 |
+mTEpNgQLKoe8ePIEPq4WHhFUovBdwlrOByUKKqxreyvWt5gkpTARz+9Lt8OjBO3rpqK8sZ |
|
500 |
+VKH3sE3de2RJM3V9PJdmZSs2b8EFK3PsUGdlMPM9pn1uk4uIItKWBmooOynuD8Ll6aPwuW |
|
501 |
+AFn3l8nLLyWdrmmEYzHWXiRjQJxy1Bi5AbHMOWiPAAAFkDPkuBkz5LgZAAAAB3NzaC1yc2 |
|
502 |
+EAAAGBALGh7ul7OHFbLg0jSZTAqqD1YJg4BL4iPE315hyql9yZCKiNy7L1eyI3/FgnU73h |
|
503 |
+KuwpLmYSJrbEedgDhFQKFPJg1wza/pyecFD839VCGkYw3tJXF1uEpRdNYULBuMGA7qsbIc |
|
504 |
+Hpa2gfy93tM4Q+342BxfCGxbKhpRR1hMDrZX4YX28SbExoLi5uAPP4HRCQ+dq5Kg3GaSFw |
|
505 |
+h7SWws1vWkKVnLPzw8OcpcJf93Qe438cn6xs+Dt0K87KoVhLIqfe5jpsJeqgh26vFfMfc7 |
|
506 |
+vPQ/x/mxuYo6sYYo243FVFt5WX8P/e4MvSeqxt9RzWq+R/BsnC0xcLghXqQ5kxKTYECyqH |
|
507 |
+vHjyBD6uFh4RVKLwXcJazgclCiqsa3sr1reYJKUwEc/vS7fDowTt66aivLGVSh97BN3Xtk |
|
508 |
+STN1fTyXZmUrNm/BBStz7FBnZTDzPaZ9bpOLiCLSlgZqKDsp7g/C5emj8LlgBZ95fJyy8l |
|
509 |
+na5phGMx1l4kY0CcctQYuQGxzDlojwAAAAMBAAEAAAF/cNVYT+Om4x9+SItcz5bOByGIOj |
|
510 |
+yWUH8f9rRjnr5ILuwabIDgvFaVG+xM1O1hWADqzMnSEcknHRkTYEsqYPykAtxFvjOFEh70 |
|
511 |
+6qRUJ+fVZkqRGEaI3oWyWKTOhcCIYImtONvb0LOv/HQ2H2AXCoeqjST1qr/xSuljBtcB8u |
|
512 |
+wxs3EqaO1yU7QoZpDcMX9plH7Rmc9nNfZcgrnktPk2deX2+Y/A5tzdVgG1IeqYp6CBMLNM |
|
513 |
+uhL0OPdDehgBoDujx+rhkZ1gpo1wcULIM94NL7VSHBPX0Lgh9T+3j1HVP+YnMAvhfOvfct |
|
514 |
+LlbJ06+TYGRAMuF2LPCAZM/m0FEyAurRgWxAjLXm+4kp2GAJXlw82deDkQ+P8cHNT6s9ZH |
|
515 |
+R5YSy3lpZ35594ZMOLR8KqVvhgJGF6i9019BiF91SDxjE+sp6dNGfN8W+64tHdDv2a0Mso |
|
516 |
++8Qjyx7sTpi++EjLU8Iy73/e4B8qbXMyheyA/UUfgMtNKShh6sLlrD9h2Sm9RFTuEAAADA |
|
517 |
+Jh3u7WfnjhhKZYbAW4TsPNXDMrB0/t7xyAQgFmko7JfESyrJSLg1cO+QMOiDgD7zuQ9RSp |
|
518 |
+NIKdPsnIna5peh979mVjb2HgnikjyJECmBpLdwZKhX7MnIvgKw5lnQXHboEtWCa1N58l7f |
|
519 |
+srzwbi9pFUuUp9dShXNffmlUCjDRsVLbK5C6+iaIQyCWFYK8mc6dpNkIoPKf+Xg+EJCIFQ |
|
520 |
+oITqeu30Gc1+M+fdZc2ghq0b6XLthh/uHEry8b68M5KglMAAAAwQDw1i+IdcvPV/3u/q9O |
|
521 |
+/kzLpKO3tbT89sc1zhjZsDNjDAGluNr6n38iq/XYRZu7UTL9BG+EgFVfIUV7XsYT5e+BPf |
|
522 |
+13VS94rzZ7maCsOlULX+VdMO2zBucHIoec9RUlRZrfB21B2W7YGMhbpoa5lN3lKJQ7afHo |
|
523 |
+dXZUMp0cTFbOmbzJgSzO2/NE7BhVwmvcUzTDJGMMKuxBO6w99YKDKRKm0PNLFDz26rWm9L |
|
524 |
+dNS2MVfVuPMTpzT26HQG4pFageq9cAAADBALzRBXdZF8kbSBa5MTUBVTTzgKQm1C772gJ8 |
|
525 |
+T01DJEXZsVtOv7mUC1/m/by6Hk4tPyvDBuGj9hHq4N7dPqGutHb1q5n0ADuoQjRW7BXw5Q |
|
526 |
+vC2EAD91xexdorIA5BgXU+qltBqzzBVzVtF7+jOZOjfzOlaTX9I5I5veyeTaTxZj1XXUzi |
|
527 |
+btBNdMEJJp7ifucYmoYAAwE7K+VlWagDEK2y8Mte9y9E+N0uO2j+h85sQt/UIb2iE/vhcg |
|
528 |
+Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB |
|
529 |
+-----END OPENSSH PRIVATE KEY----- |
|
530 |
+""", |
|
531 |
+ private_key_blob=bytes.fromhex(""" |
|
532 |
+ 00 00 00 07 73 73 68 2d 72 73 61 |
|
533 |
+ 00 00 01 81 00 |
|
534 |
+ b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa a0 |
|
535 |
+ f5 60 98 38 04 be 22 3c 4d f5 e6 1c aa 97 dc 99 |
|
536 |
+ 08 a8 8d cb b2 f5 7b 22 37 fc 58 27 53 bd e1 2a |
|
537 |
+ ec 29 2e 66 12 26 b6 c4 79 d8 03 84 54 0a 14 f2 |
|
538 |
+ 60 d7 0c da fe 9c 9e 70 50 fc df d5 42 1a 46 30 |
|
539 |
+ de d2 57 17 5b 84 a5 17 4d 61 42 c1 b8 c1 80 ee |
|
540 |
+ ab 1b 21 c1 e9 6b 68 1f cb dd ed 33 84 3e df 8d |
|
541 |
+ 81 c5 f0 86 c5 b2 a1 a5 14 75 84 c0 eb 65 7e 18 |
|
542 |
+ 5f 6f 12 6c 4c 68 2e 2e 6e 00 f3 f8 1d 10 90 f9 |
|
543 |
+ da b9 2a 0d c6 69 21 70 87 b4 96 c2 cd 6f 5a 42 |
|
544 |
+ 95 9c b3 f3 c3 c3 9c a5 c2 5f f7 74 1e e3 7f 1c |
|
545 |
+ 9f ac 6c f8 3b 74 2b ce ca a1 58 4b 22 a7 de e6 |
|
546 |
+ 3a 6c 25 ea a0 87 6e af 15 f3 1f 73 bb cf 43 fc |
|
547 |
+ 7f 9b 1b 98 a3 ab 18 62 8d b8 dc 55 45 b7 95 97 |
|
548 |
+ f0 ff de e0 cb d2 7a ac 6d f5 1c d6 ab e4 7f 06 |
|
549 |
+ c9 c2 d3 17 0b 82 15 ea 43 99 31 29 36 04 0b 2a |
|
550 |
+ 87 bc 78 f2 04 3e ae 16 1e 11 54 a2 f0 5d c2 5a |
|
551 |
+ ce 07 25 0a 2a ac 6b 7b 2b d6 b7 98 24 a5 30 11 |
|
552 |
+ cf ef 4b b7 c3 a3 04 ed eb a6 a2 bc b1 95 4a 1f |
|
553 |
+ 7b 04 dd d7 b6 44 93 37 57 d3 c9 76 66 52 b3 66 |
|
554 |
+ fc 10 52 b7 3e c5 06 76 53 0f 33 da 67 d6 e9 38 |
|
555 |
+ b8 82 2d 29 60 66 a2 83 b2 9e e0 fc 2e 5e 9a 3f |
|
556 |
+ 0b 96 00 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31 |
|
557 |
+ d6 5e 24 63 40 9c 72 d4 18 b9 01 b1 cc 39 68 8f |
|
558 |
+ 00 00 00 03 01 00 01 |
|
559 |
+ 00 00 01 7f |
|
560 |
+ 70 d5 58 4f e3 a6 e3 1f 7e 48 8b 5c cf 96 ce |
|
561 |
+ 07 21 88 3a 3c 96 50 7f 1f f6 b4 63 9e be 48 2e |
|
562 |
+ ec 1a 6c 80 e0 bc 56 95 1b ec 4c d4 ed 61 58 00 |
|
563 |
+ ea cc c9 d2 11 c9 27 1d 19 13 60 4b 2a 60 fc a4 |
|
564 |
+ 02 dc 45 be 33 85 12 1e f4 ea a4 54 27 e7 d5 66 |
|
565 |
+ 4a 91 18 46 88 de 85 b2 58 a4 ce 85 c0 88 60 89 |
|
566 |
+ ad 38 db db d0 b3 af fc 74 36 1f 60 17 0a 87 aa |
|
567 |
+ 8d 24 f5 aa bf f1 4a e9 63 06 d7 01 f2 ec 31 b3 |
|
568 |
+ 71 2a 68 ed 72 53 b4 28 66 90 dc 31 7f 69 94 7e |
|
569 |
+ d1 99 cf 67 35 f6 5c 82 b9 e4 b4 f9 36 75 e5 f6 |
|
570 |
+ f9 8f c0 e6 dc dd 56 01 b5 21 ea 98 a7 a0 81 30 |
|
571 |
+ b3 4c ba 12 f4 38 f7 43 7a 18 01 a0 3b a3 c7 ea |
|
572 |
+ e1 91 9d 60 a6 8d 70 71 42 c8 33 de 0d 2f b5 52 |
|
573 |
+ 1c 13 d7 d0 b8 21 f5 3f b7 8f 51 d5 3f e6 27 30 |
|
574 |
+ 0b e1 7c eb df 72 d2 e5 6c 9d 3a f9 36 06 44 03 |
|
575 |
+ 2e 17 62 cf 08 06 4c fe 6d 05 13 20 2e ad 18 16 |
|
576 |
+ c4 08 cb 5e 6f b8 92 9d 86 00 95 e5 c3 cd 9d 78 |
|
577 |
+ 39 10 f8 ff 1c 1c d4 fa b3 d6 47 47 96 12 cb 79 |
|
578 |
+ 69 67 7e 79 f7 86 4c 38 b4 7c 2a a5 6f 86 02 46 |
|
579 |
+ 17 a8 bd d3 5f 41 88 5f 75 48 3c 63 13 eb 29 e9 |
|
580 |
+ d3 46 7c df 16 fb ae 2d 1d d0 ef d9 ad 0c b2 8f |
|
581 |
+ bc 42 3c b1 ee c4 e9 8b ef 84 8c b5 3c 23 2e f7 |
|
582 |
+ fd ee 01 f2 a6 d7 33 28 5e c8 0f d4 51 f8 0c b4 |
|
583 |
+ d2 92 86 1e ac 2e 5a c3 f6 1d 92 9b d4 45 4e e1 |
|
584 |
+ 00 00 00 c0 |
|
585 |
+ 26 1d ee ed 67 e7 8e 18 4a 65 86 c0 5b 84 ec 3c |
|
586 |
+ d5 c3 32 b0 74 fe de f1 c8 04 20 16 69 28 ec 97 |
|
587 |
+ c4 4b 2a c9 48 b8 35 70 ef 90 30 e8 83 80 3e f3 |
|
588 |
+ b9 0f 51 4a 93 48 29 d3 ec 9c 89 da e6 97 a1 f7 |
|
589 |
+ bf 66 56 36 f6 1e 09 e2 92 3c 89 10 29 81 a4 b7 |
|
590 |
+ 70 64 a8 57 ec c9 c8 be 02 b0 e6 59 d0 5c 76 e8 |
|
591 |
+ 12 d5 82 6b 53 79 f2 5e df b2 bc f0 6e 2f 69 15 |
|
592 |
+ 4b 94 a7 d7 52 85 73 5f 7e 69 54 0a 30 d1 b1 52 |
|
593 |
+ db 2b 90 ba fa 26 88 43 20 96 15 82 bc 99 ce 9d |
|
594 |
+ a4 d9 08 a0 f2 9f f9 78 3e 10 90 88 15 0a 08 4e |
|
595 |
+ a7 ae df 41 9c d7 e3 3e 7d d6 5c da 08 6a d1 be |
|
596 |
+ 97 2e d8 61 fe e1 c4 af 2f 1b eb c3 39 2a 09 4c |
|
597 |
+ 00 00 00 c1 00 |
|
598 |
+ f0 d6 2f 88 75 cb cf 57 fd ee fe af 4e fe 4c cb |
|
599 |
+ a4 a3 b7 b5 b4 fc f6 c7 35 ce 18 d9 b0 33 63 0c |
|
600 |
+ 01 a5 b8 da fa 9f 7f 22 ab f5 d8 45 9b bb 51 32 |
|
601 |
+ fd 04 6f 84 80 55 5f 21 45 7b 5e c6 13 e5 ef 81 |
|
602 |
+ 3d fd 77 55 2f 78 af 36 7b 99 a0 ac 3a 55 0b 5f |
|
603 |
+ e5 5d 30 ed b3 06 e7 07 22 87 9c f5 15 25 45 9a |
|
604 |
+ df 07 6d 41 d9 6e d8 18 c8 5b a6 86 b9 94 dd e5 |
|
605 |
+ 28 94 3b 69 f1 e8 75 76 54 32 9d 1c 4c 56 ce 99 |
|
606 |
+ bc c9 81 2c ce db f3 44 ec 18 55 c2 6b dc 53 34 |
|
607 |
+ c3 24 63 0c 2a ec 41 3b ac 3d f5 82 83 29 12 a6 |
|
608 |
+ d0 f3 4b 14 3c f6 ea b5 a6 f4 b7 4d 4b 63 15 7d |
|
609 |
+ 5b 8f 31 3a 73 4f 6e 87 40 6e 29 15 a8 1e ab d7 |
|
610 |
+ 00 00 00 c1 00 |
|
611 |
+ bc d1 05 77 59 17 c9 1b 48 16 b9 31 35 01 55 34 |
|
612 |
+ f3 80 a4 26 d4 2e fb da 02 7c 4f 4d 43 24 45 d9 |
|
613 |
+ b1 5b 4e bf b9 94 0b 5f e6 fd bc ba 1e 4e 2d 3f |
|
614 |
+ 2b c3 06 e1 a3 f6 11 ea e0 de dd 3e a1 ae b4 76 |
|
615 |
+ f5 ab 99 f4 00 3b a8 42 34 56 ec 15 f0 e5 0b c2 |
|
616 |
+ d8 40 03 f7 5c 5e c5 da 2b 20 0e 41 81 75 3e aa |
|
617 |
+ 5b 41 ab 3c c1 57 35 6d 17 bf a3 39 93 a3 7f 33 |
|
618 |
+ a5 69 35 fd 23 92 39 bd ec 9e 4d a4 f1 66 3d 57 |
|
619 |
+ 5d 4c e2 6e d0 4d 74 c1 09 26 9e e2 7e e7 18 9a |
|
620 |
+ 86 00 03 01 3b 2b e5 65 59 a8 03 10 ad b2 f0 cb |
|
621 |
+ 5e f7 2f 44 f8 dd 2e 3b 68 fe 87 ce 6c 42 df d4 |
|
622 |
+ 21 bd a2 13 fb e1 72 00 60 a7 ad 78 d9 69 d2 09 |
|
623 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
624 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
625 |
+"""), |
|
626 |
+ 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 |
|
627 |
+""", |
|
628 |
+ public_key_data=bytes.fromhex(""" |
|
629 |
+ 00 00 00 07 73 73 68 2d 72 73 61 |
|
630 |
+ 00 00 00 03 01 00 01 |
|
631 |
+ 00 00 01 81 00 |
|
632 |
+ b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa a0 |
|
633 |
+ f5 60 98 38 04 be 22 3c 4d f5 e6 1c aa 97 dc 99 |
|
634 |
+ 08 a8 8d cb b2 f5 7b 22 37 fc 58 27 53 bd e1 2a |
|
635 |
+ ec 29 2e 66 12 26 b6 c4 79 d8 03 84 54 0a 14 f2 |
|
636 |
+ 60 d7 0c da fe 9c 9e 70 50 fc df d5 42 1a 46 30 |
|
637 |
+ de d2 57 17 5b 84 a5 17 4d 61 42 c1 b8 c1 80 ee |
|
638 |
+ ab 1b 21 c1 e9 6b 68 1f cb dd ed 33 84 3e df 8d |
|
639 |
+ 81 c5 f0 86 c5 b2 a1 a5 14 75 84 c0 eb 65 7e 18 |
|
640 |
+ 5f 6f 12 6c 4c 68 2e 2e 6e 00 f3 f8 1d 10 90 f9 |
|
641 |
+ da b9 2a 0d c6 69 21 70 87 b4 96 c2 cd 6f 5a 42 |
|
642 |
+ 95 9c b3 f3 c3 c3 9c a5 c2 5f f7 74 1e e3 7f 1c |
|
643 |
+ 9f ac 6c f8 3b 74 2b ce ca a1 58 4b 22 a7 de e6 |
|
644 |
+ 3a 6c 25 ea a0 87 6e af 15 f3 1f 73 bb cf 43 fc |
|
645 |
+ 7f 9b 1b 98 a3 ab 18 62 8d b8 dc 55 45 b7 95 97 |
|
646 |
+ f0 ff de e0 cb d2 7a ac 6d f5 1c d6 ab e4 7f 06 |
|
647 |
+ c9 c2 d3 17 0b 82 15 ea 43 99 31 29 36 04 0b 2a |
|
648 |
+ 87 bc 78 f2 04 3e ae 16 1e 11 54 a2 f0 5d c2 5a |
|
649 |
+ ce 07 25 0a 2a ac 6b 7b 2b d6 b7 98 24 a5 30 11 |
|
650 |
+ cf ef 4b b7 c3 a3 04 ed eb a6 a2 bc b1 95 4a 1f |
|
651 |
+ 7b 04 dd d7 b6 44 93 37 57 d3 c9 76 66 52 b3 66 |
|
652 |
+ fc 10 52 b7 3e c5 06 76 53 0f 33 da 67 d6 e9 38 |
|
653 |
+ b8 82 2d 29 60 66 a2 83 b2 9e e0 fc 2e 5e 9a 3f |
|
654 |
+ 0b 96 00 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31 |
|
655 |
+ d6 5e 24 63 40 9c 72 d4 18 b9 01 b1 cc 39 68 8f |
|
656 |
+"""), |
|
657 |
+ expected_signatures={ |
|
658 |
+ SSHTestKeyDeterministicSignatureClass.SPEC: SSHTestKeyDeterministicSignature( |
|
659 |
+ signature=bytes.fromhex(""" |
|
660 |
+ 00 00 00 07 73 73 68 2d 72 73 61 |
|
661 |
+ 00 00 01 80 |
|
662 |
+ a2 10 7c 2e f6 bb 53 a8 74 2a a1 19 99 ad 81 be |
|
663 |
+ 79 9c ed d6 9d 09 4e 6e c5 18 48 33 90 77 99 68 |
|
664 |
+ f7 9e 03 5a cd 4e 18 eb 89 7d 85 a2 ee ae 4a 92 |
|
665 |
+ f6 6f ce b9 fe 86 7f 2a 6b 31 da 6e 1a fe a2 a5 |
|
666 |
+ 88 b8 44 7f a1 76 73 b3 ec 75 b5 d0 a6 b9 15 97 |
|
667 |
+ 65 09 13 7d 94 21 d1 fb 5d 0f 8b 23 04 77 c2 c3 |
|
668 |
+ 55 22 b1 a0 09 8a f5 38 2a d6 7f 1b 87 29 a0 25 |
|
669 |
+ d3 25 6f cb 64 61 07 98 dc 14 c5 84 f8 92 24 5e |
|
670 |
+ 50 11 6b 49 e5 f0 cc 29 cb 29 a9 19 d8 a7 71 1f |
|
671 |
+ 91 0b 05 b1 01 4b c2 5f 00 a5 b6 21 bf f8 2c 9d |
|
672 |
+ 67 9b 47 3b 0a 49 6b 79 2d fc 1d ec 0c b0 e5 27 |
|
673 |
+ 22 d5 a9 f8 d3 c3 f9 df 48 68 e9 fb ef 3c dc 26 |
|
674 |
+ bf cf ea 29 43 01 a6 e3 c5 51 95 f4 66 6d 8a 55 |
|
675 |
+ e2 47 ec e8 30 45 4c ae 47 e7 c9 a4 21 8b 64 ba |
|
676 |
+ b6 88 f6 21 f8 73 b9 cb 11 a1 78 75 92 c6 5a e5 |
|
677 |
+ 64 fe ed 42 d9 95 99 e6 2b 6f 3c 16 3c 28 74 a4 |
|
678 |
+ 72 2f 0d 3f 2c 33 67 aa 35 19 8e e7 b5 11 2f b3 |
|
679 |
+ f7 6a c5 02 e2 6f a3 42 e3 62 19 99 03 ea a5 20 |
|
680 |
+ e7 a1 e3 bc c8 06 a3 b5 7c d6 76 5d df 6f 60 46 |
|
681 |
+ 83 2a 08 00 d6 d3 d9 a4 c1 41 8c f8 60 56 45 81 |
|
682 |
+ da 3b a2 16 1f 9e 4e 75 83 17 da c3 53 c3 3e 19 |
|
683 |
+ a4 1b bc d2 29 b8 78 61 2b 78 e6 b1 52 b0 d5 ec |
|
684 |
+ de 69 2c 48 62 d9 fd d1 9b 6b b0 49 db d3 ff 38 |
|
685 |
+ e7 10 d9 2d ce 9f 0d 5e 09 7b 37 d2 7b c3 bf ce |
|
686 |
+"""), |
|
687 |
+ derived_passphrase=rb"""ohB8Lva7U6h0KqEZma2Bvnmc7dadCU5uxRhIM5B3mWj3ngNazU4Y64l9haLurkqS9m/Ouf6GfyprMdpuGv6ipYi4RH+hdnOz7HW10Ka5FZdlCRN9lCHR+10PiyMEd8LDVSKxoAmK9Tgq1n8bhymgJdMlb8tkYQeY3BTFhPiSJF5QEWtJ5fDMKcspqRnYp3EfkQsFsQFLwl8ApbYhv/gsnWebRzsKSWt5Lfwd7Ayw5Sci1an408P530ho6fvvPNwmv8/qKUMBpuPFUZX0Zm2KVeJH7OgwRUyuR+fJpCGLZLq2iPYh+HO5yxGheHWSxlrlZP7tQtmVmeYrbzwWPCh0pHIvDT8sM2eqNRmO57URL7P3asUC4m+jQuNiGZkD6qUg56HjvMgGo7V81nZd329gRoMqCADW09mkwUGM+GBWRYHaO6IWH55OdYMX2sNTwz4ZpBu80im4eGEreOaxUrDV7N5pLEhi2f3Rm2uwSdvT/zjnENktzp8NXgl7N9J7w7/O""", |
|
688 |
+ ), |
|
689 |
+ }, |
|
690 |
+ ), |
|
691 |
+ "dsa1024": SSHTestKey( |
|
692 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
693 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH |
|
694 |
+NzAAAAgQC7KAZXqBGNVLBQPrcMYAoNW54BhD8aIhe7BDWYzJcsaMt72VKSkguZ8+XR7nRa |
|
695 |
+0C/ZsBi+uJp0dpxy9ZMTOWX4u5YPMeQcXEdGExZIfimGqSOAsy6fCld2IfJZJZExcCmhe9 |
|
696 |
+Ssjsd3YSAPJRluOXFQc95MZoR5hMwlIDD8QzrE7QAAABUA99nOZOgd7aHMVGoXpUEBcn7H |
|
697 |
+ossAAACALr2Ag3hxM3rKdxzVUw8fX0VVPXO+3+Kr8hGe0Kc/7NwVaBVL1GQ8fenBuWynpA |
|
698 |
+UbH0wo3h1wkB/8hX6p+S8cnu5rIBlUuVNwLw/bIYohK98LfqTYK/V+g6KD+8m34wvEiXZm |
|
699 |
+qywY54n2bksch1Nqvj/tNpLzExSx/XS0kSM1aigAAACAbQNRPcVEuGDrEcf+xg5tgAejPX |
|
700 |
+BPXr/Jss+Chk64km3mirMYjAWyWYtVcgT+7hOYxtYRin8LyMLqKRmqa0Q5UrvDfChgLhvs |
|
701 |
+G9YSb/Mpw5qm8PiHSafwhkaz/te3+8hKogqoe7sd+tCF06IpJr5k70ACiNtRGqssNF8Elr |
|
702 |
+l1efYAAAH4swlfVrMJX1YAAAAHc3NoLWRzcwAAAIEAuygGV6gRjVSwUD63DGAKDVueAYQ/ |
|
703 |
+GiIXuwQ1mMyXLGjLe9lSkpILmfPl0e50WtAv2bAYvriadHaccvWTEzll+LuWDzHkHFxHRh |
|
704 |
+MWSH4phqkjgLMunwpXdiHyWSWRMXApoXvUrI7Hd2EgDyUZbjlxUHPeTGaEeYTMJSAw/EM6 |
|
705 |
+xO0AAAAVAPfZzmToHe2hzFRqF6VBAXJ+x6LLAAAAgC69gIN4cTN6yncc1VMPH19FVT1zvt |
|
706 |
+/iq/IRntCnP+zcFWgVS9RkPH3pwblsp6QFGx9MKN4dcJAf/IV+qfkvHJ7uayAZVLlTcC8P |
|
707 |
+2yGKISvfC36k2Cv1foOig/vJt+MLxIl2ZqssGOeJ9m5LHIdTar4/7TaS8xMUsf10tJEjNW |
|
708 |
+ooAAAAgG0DUT3FRLhg6xHH/sYObYAHoz1wT16/ybLPgoZOuJJt5oqzGIwFslmLVXIE/u4T |
|
709 |
+mMbWEYp/C8jC6ikZqmtEOVK7w3woYC4b7BvWEm/zKcOapvD4h0mn8IZGs/7Xt/vISqIKqH |
|
710 |
+u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW |
|
711 |
+0gAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UBAgMEBQYH |
|
712 |
+-----END OPENSSH PRIVATE KEY----- |
|
713 |
+""", |
|
714 |
+ private_key_blob=bytes.fromhex(""" |
|
715 |
+ 00 00 00 07 73 73 68 2d 64 73 73 |
|
716 |
+ 00 00 00 81 00 |
|
717 |
+ bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d |
|
718 |
+ 5b 9e 01 84 3f 1a 22 17 bb 04 35 98 cc 97 2c 68 |
|
719 |
+ cb 7b d9 52 92 92 0b 99 f3 e5 d1 ee 74 5a d0 2f |
|
720 |
+ d9 b0 18 be b8 9a 74 76 9c 72 f5 93 13 39 65 f8 |
|
721 |
+ bb 96 0f 31 e4 1c 5c 47 46 13 16 48 7e 29 86 a9 |
|
722 |
+ 23 80 b3 2e 9f 0a 57 76 21 f2 59 25 91 31 70 29 |
|
723 |
+ a1 7b d4 ac 8e c7 77 61 20 0f 25 19 6e 39 71 50 |
|
724 |
+ 73 de 4c 66 84 79 84 cc 25 20 30 fc 43 3a c4 ed |
|
725 |
+ 00 00 00 15 00 f7 d9 ce 64 |
|
726 |
+ e8 1d ed a1 cc 54 6a 17 a5 41 01 72 7e c7 a2 cb |
|
727 |
+ 00 00 00 80 |
|
728 |
+ 2e bd 80 83 78 71 33 7a ca 77 1c d5 53 0f 1f 5f |
|
729 |
+ 45 55 3d 73 be df e2 ab f2 11 9e d0 a7 3f ec dc |
|
730 |
+ 15 68 15 4b d4 64 3c 7d e9 c1 b9 6c a7 a4 05 1b |
|
731 |
+ 1f 4c 28 de 1d 70 90 1f fc 85 7e a9 f9 2f 1c 9e |
|
732 |
+ ee 6b 20 19 54 b9 53 70 2f 0f db 21 8a 21 2b df |
|
733 |
+ 0b 7e a4 d8 2b f5 7e 83 a2 83 fb c9 b7 e3 0b c4 |
|
734 |
+ 89 76 66 ab 2c 18 e7 89 f6 6e 4b 1c 87 53 6a be |
|
735 |
+ 3f ed 36 92 f3 13 14 b1 fd 74 b4 91 23 35 6a 28 |
|
736 |
+ 00 00 00 80 |
|
737 |
+ 6d 03 51 3d c5 44 b8 60 eb 11 c7 fe c6 0e 6d 80 |
|
738 |
+ 07 a3 3d 70 4f 5e bf c9 b2 cf 82 86 4e b8 92 6d |
|
739 |
+ e6 8a b3 18 8c 05 b2 59 8b 55 72 04 fe ee 13 98 |
|
740 |
+ c6 d6 11 8a 7f 0b c8 c2 ea 29 19 aa 6b 44 39 52 |
|
741 |
+ bb c3 7c 28 60 2e 1b ec 1b d6 12 6f f3 29 c3 9a |
|
742 |
+ a6 f0 f8 87 49 a7 f0 86 46 b3 fe d7 b7 fb c8 4a |
|
743 |
+ a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef |
|
744 |
+ 40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6 |
|
745 |
+ 00 00 00 15 00 c9 1d f7 a7 |
|
746 |
+ 8f 81 09 f5 69 1e 86 97 49 6a d3 c1 96 a0 96 d2 |
|
747 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
748 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
749 |
+"""), |
|
750 |
+ public_key=rb"""ssh-dss AAAAB3NzaC1kc3MAAACBALsoBleoEY1UsFA+twxgCg1bngGEPxoiF7sENZjMlyxoy3vZUpKSC5nz5dHudFrQL9mwGL64mnR2nHL1kxM5Zfi7lg8x5BxcR0YTFkh+KYapI4CzLp8KV3Yh8lklkTFwKaF71KyOx3dhIA8lGW45cVBz3kxmhHmEzCUgMPxDOsTtAAAAFQD32c5k6B3tocxUahelQQFyfseiywAAAIAuvYCDeHEzesp3HNVTDx9fRVU9c77f4qvyEZ7Qpz/s3BVoFUvUZDx96cG5bKekBRsfTCjeHXCQH/yFfqn5Lxye7msgGVS5U3AvD9shiiEr3wt+pNgr9X6DooP7ybfjC8SJdmarLBjnifZuSxyHU2q+P+02kvMTFLH9dLSRIzVqKAAAAIBtA1E9xUS4YOsRx/7GDm2AB6M9cE9ev8myz4KGTriSbeaKsxiMBbJZi1VyBP7uE5jG1hGKfwvIwuopGaprRDlSu8N8KGAuG+wb1hJv8ynDmqbw+IdJp/CGRrP+17f7yEqiCqh7ux360IXToikmvmTvQAKI21Eaqyw0XwSWuXV59g== test key without passphrase |
|
751 |
+""", |
|
752 |
+ public_key_data=bytes.fromhex(""" |
|
753 |
+ 00 00 00 07 73 73 68 2d 64 73 73 |
|
754 |
+ 00 00 00 81 00 |
|
755 |
+ bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d |
|
756 |
+ 5b 9e 01 84 3f 1a 22 17 bb 04 35 98 cc 97 2c 68 |
|
757 |
+ cb 7b d9 52 92 92 0b 99 f3 e5 d1 ee 74 5a d0 2f |
|
758 |
+ d9 b0 18 be b8 9a 74 76 9c 72 f5 93 13 39 65 f8 |
|
759 |
+ bb 96 0f 31 e4 1c 5c 47 46 13 16 48 7e 29 86 a9 |
|
760 |
+ 23 80 b3 2e 9f 0a 57 76 21 f2 59 25 91 31 70 29 |
|
761 |
+ a1 7b d4 ac 8e c7 77 61 20 0f 25 19 6e 39 71 50 |
|
762 |
+ 73 de 4c 66 84 79 84 cc 25 20 30 fc 43 3a c4 ed |
|
763 |
+ 00 00 00 15 00 f7 d9 ce 64 |
|
764 |
+ e8 1d ed a1 cc 54 6a 17 a5 41 01 72 7e c7 a2 cb |
|
765 |
+ 00 00 00 80 |
|
766 |
+ 2e bd 80 83 78 71 33 7a ca 77 1c d5 53 0f 1f 5f |
|
767 |
+ 45 55 3d 73 be df e2 ab f2 11 9e d0 a7 3f ec dc |
|
768 |
+ 15 68 15 4b d4 64 3c 7d e9 c1 b9 6c a7 a4 05 1b |
|
769 |
+ 1f 4c 28 de 1d 70 90 1f fc 85 7e a9 f9 2f 1c 9e |
|
770 |
+ ee 6b 20 19 54 b9 53 70 2f 0f db 21 8a 21 2b df |
|
771 |
+ 0b 7e a4 d8 2b f5 7e 83 a2 83 fb c9 b7 e3 0b c4 |
|
772 |
+ 89 76 66 ab 2c 18 e7 89 f6 6e 4b 1c 87 53 6a be |
|
773 |
+ 3f ed 36 92 f3 13 14 b1 fd 74 b4 91 23 35 6a 28 |
|
774 |
+ 00 00 00 80 |
|
775 |
+ 6d 03 51 3d c5 44 b8 60 eb 11 c7 fe c6 0e 6d 80 |
|
776 |
+ 07 a3 3d 70 4f 5e bf c9 b2 cf 82 86 4e b8 92 6d |
|
777 |
+ e6 8a b3 18 8c 05 b2 59 8b 55 72 04 fe ee 13 98 |
|
778 |
+ c6 d6 11 8a 7f 0b c8 c2 ea 29 19 aa 6b 44 39 52 |
|
779 |
+ bb c3 7c 28 60 2e 1b ec 1b d6 12 6f f3 29 c3 9a |
|
780 |
+ a6 f0 f8 87 49 a7 f0 86 46 b3 fe d7 b7 fb c8 4a |
|
781 |
+ a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef |
|
782 |
+ 40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6 |
|
783 |
+"""), |
|
784 |
+ expected_signatures={ |
|
785 |
+ SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
786 |
+ signature=bytes.fromhex(""" |
|
787 |
+ 00 00 00 07 73 73 68 2d 64 73 73 |
|
788 |
+ 00 00 00 28 11 5f 4d 13 c2 ee 61 97 |
|
789 |
+ 1e f6 23 14 3b 2b dd cf 06 c0 71 13 cc ac 34 19 |
|
790 |
+ ad 36 8d 79 aa 25 fb 5e 4f ea fe 6b 5b fa 57 42 |
|
791 |
+"""), |
|
792 |
+ derived_passphrase=rb"""EV9NE8LuYZce9iMUOyvdzwbAcRPMrDQZrTaNeaol+15P6v5rW/pXQg==""", |
|
793 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
794 |
+ ), |
|
795 |
+ SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
796 |
+ signature=bytes.fromhex(""" |
|
797 |
+ 00 00 00 07 73 73 68 2d 64 73 73 |
|
798 |
+ 00 00 00 28 0b f7 a8 ab 89 f5 b6 c4 |
|
799 |
+ 1c 9b 78 2c 46 35 69 e2 88 b7 eb 55 37 48 7f 6d |
|
800 |
+ 49 a1 e6 de 58 1a 04 eb e6 28 99 0e 3c fd 3b 48 |
|
801 |
+"""), |
|
802 |
+ derived_passphrase=rb"""C/eoq4n1tsQcm3gsRjVp4oi361U3SH9tSaHm3lgaBOvmKJkOPP07SA==""", |
|
803 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
804 |
+ ), |
|
805 |
+ }, |
|
806 |
+ ), |
|
807 |
+ "ecdsa256": SSHTestKey( |
|
808 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
809 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS |
|
810 |
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTLbU0zDwsk2Dvp+VYIrsNVf5gWwz2S |
|
811 |
+3SZ8TbxiQRkpnGSVqyIoHJOJc+NQItAa7xlJ/8Z6gfz57Z3apUkaMJm6AAAAuKeY+YinmP |
|
812 |
+mIAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5 |
|
813 |
+Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmb |
|
814 |
+oAAAAhAKIl/3n0pKVIxpZkXTGtii782Qr4yIcvHdpxjO/QsIqKAAAAG3Rlc3Qga2V5IHdp |
|
815 |
+dGhvdXQgcGFzc3BocmFzZQECAwQ= |
|
816 |
+-----END OPENSSH PRIVATE KEY----- |
|
817 |
+""", |
|
818 |
+ private_key_blob=bytes.fromhex(""" |
|
819 |
+ 00 00 00 13 65 63 64 |
|
820 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
821 |
+ 00 00 00 08 6e 69 73 74 70 32 35 36 |
|
822 |
+ 00 00 00 41 04 |
|
823 |
+ cb 6d 4d 33 0f 0b 24 d8 3b e9 f9 56 08 ae c3 55 |
|
824 |
+ 7f 98 16 c3 3d 92 dd 26 7c 4d bc 62 41 19 29 9c |
|
825 |
+ 64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19 |
|
826 |
+ 49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba |
|
827 |
+ 00 00 00 21 00 |
|
828 |
+ a2 25 ff 79 f4 a4 a5 48 c6 96 64 5d 31 ad 8a 2e |
|
829 |
+ fc d9 0a f8 c8 87 2f 1d da 71 8c ef d0 b0 8a 8a |
|
830 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
831 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
832 |
+"""), |
|
833 |
+ public_key=rb"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmbo= test key without passphrase |
|
834 |
+""", |
|
835 |
+ public_key_data=bytes.fromhex(""" |
|
836 |
+ 00 00 00 13 65 63 64 |
|
837 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
838 |
+ 00 00 00 08 6e 69 73 74 70 32 35 36 |
|
839 |
+ 00 00 00 41 04 |
|
840 |
+ cb 6d 4d 33 0f 0b 24 d8 3b e9 f9 56 08 ae c3 55 |
|
841 |
+ 7f 98 16 c3 3d 92 dd 26 7c 4d bc 62 41 19 29 9c |
|
842 |
+ 64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19 |
|
843 |
+ 49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba |
|
844 |
+"""), |
|
845 |
+ expected_signatures={ |
|
846 |
+ SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
847 |
+ signature=bytes.fromhex(""" |
|
848 |
+ 00 00 00 13 65 63 64 |
|
849 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
850 |
+ 00 00 00 49 |
|
851 |
+ 00 00 00 20 |
|
852 |
+ 22 ad 23 8a 9c 5d ca 4e ea 73 e7 29 77 ab a8 b2 |
|
853 |
+ 2e 01 d8 de 11 ae c9 b3 57 ce d5 84 9c 85 73 eb |
|
854 |
+ 00 00 00 21 00 |
|
855 |
+ 9b 1a cb dd 45 89 f0 37 95 9c a2 d8 ac c3 f7 71 |
|
856 |
+ 55 33 50 86 9e cb 3a 95 e4 68 80 1a 9d d6 d5 bc |
|
857 |
+"""), |
|
858 |
+ derived_passphrase=rb"""AAAAICKtI4qcXcpO6nPnKXerqLIuAdjeEa7Js1fO1YSchXPrAAAAIQCbGsvdRYnwN5Wcotisw/dxVTNQhp7LOpXkaIAandbVvA==""", |
|
859 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
860 |
+ ), |
|
861 |
+ SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
862 |
+ signature=bytes.fromhex(""" |
|
863 |
+ 00 00 00 13 65 63 64 |
|
864 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36 |
|
865 |
+ 00 00 00 49 |
|
866 |
+ 00 00 00 21 00 |
|
867 |
+ b7 9e 4f ec ec 9b 77 dd 12 d9 43 a2 f5 bf b5 34 |
|
868 |
+ 91 e0 89 44 e6 20 48 36 fa 75 22 77 86 38 de 21 |
|
869 |
+ 00 00 00 20 |
|
870 |
+ 3f d8 04 0f fa f5 bc d2 26 e0 4c 0c 77 5d 0e 08 |
|
871 |
+ ec 30 04 8e 42 58 41 96 f6 7e 4f d2 14 39 f4 87 |
|
872 |
+"""), |
|
873 |
+ derived_passphrase=rb"""AAAAIQC3nk/s7Jt33RLZQ6L1v7U0keCJROYgSDb6dSJ3hjjeIQAAACA/2AQP+vW80ibgTAx3XQ4I7DAEjkJYQZb2fk/SFDn0hw==""", |
|
874 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
875 |
+ ), |
|
876 |
+ }, |
|
877 |
+ ), |
|
878 |
+ "ecdsa384": SSHTestKey( |
|
879 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
880 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS |
|
881 |
+1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSgkOjkAvq7v5vHuj3KBL4/EAWcn5hZ |
|
882 |
+DyKcbyV0eBMGFq7hKXQlZqIahLVqeMR0QqmkxNJ2rly2VHcXneq3vZ+9fIsWCOdYk5WP3N |
|
883 |
+ZPzv911Xn7wbEkC7QndD5zKlm4pBUAAADomhj+IZoY/iEAAAATZWNkc2Etc2hhMi1uaXN0 |
|
884 |
+cDM4NAAAAAhuaXN0cDM4NAAAAGEEoJDo5AL6u7+bx7o9ygS+PxAFnJ+YWQ8inG8ldHgTBh |
|
885 |
+au4Sl0JWaiGoS1anjEdEKppMTSdq5ctlR3F53qt72fvXyLFgjnWJOVj9zWT87/ddV5+8Gx |
|
886 |
+JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B |
|
887 |
+2OdqVOVRkwZWRROz0AAAAbdGVzdCBrZXkgd2l0aG91dCBwYXNzcGhyYXNlAQIDBA== |
|
888 |
+-----END OPENSSH PRIVATE KEY----- |
|
889 |
+""", |
|
890 |
+ private_key_blob=bytes.fromhex(""" |
|
891 |
+ 00 00 00 13 65 63 64 |
|
892 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
893 |
+ 00 00 00 08 6e 69 73 74 70 33 38 34 |
|
894 |
+ 00 00 00 61 04 |
|
895 |
+ a0 90 e8 e4 02 fa bb bf 9b c7 ba 3d ca 04 be 3f |
|
896 |
+ 10 05 9c 9f 98 59 0f 22 9c 6f 25 74 78 13 06 16 |
|
897 |
+ ae e1 29 74 25 66 a2 1a 84 b5 6a 78 c4 74 42 a9 |
|
898 |
+ a4 c4 d2 76 ae 5c b6 54 77 17 9d ea b7 bd 9f bd |
|
899 |
+ 7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5 |
|
900 |
+ 79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15 |
|
901 |
+ 00 00 00 31 00 |
|
902 |
+ f9 b1 3c bc a7 e0 75 72 7f c3 84 e9 97 aa eb a2 |
|
903 |
+ d4 17 31 bc 04 aa cf 37 af 90 19 5b 42 80 5a ef |
|
904 |
+ 7c b4 e0 1d 8e 76 a5 4e 55 19 30 65 64 51 3b 3d |
|
905 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
906 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
907 |
+"""), |
|
908 |
+ public_key=rb"""ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKCQ6OQC+ru/m8e6PcoEvj8QBZyfmFkPIpxvJXR4EwYWruEpdCVmohqEtWp4xHRCqaTE0nauXLZUdxed6re9n718ixYI51iTlY/c1k/O/3XVefvBsSQLtCd0PnMqWbikFQ== test key without passphrase |
|
909 |
+""", |
|
910 |
+ public_key_data=bytes.fromhex(""" |
|
911 |
+ 00 00 00 13 65 63 64 |
|
912 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
913 |
+ 00 00 00 08 6e 69 73 74 70 33 38 34 |
|
914 |
+ 00 00 00 61 04 |
|
915 |
+ a0 90 e8 e4 02 fa bb bf 9b c7 ba 3d ca 04 be 3f |
|
916 |
+ 10 05 9c 9f 98 59 0f 22 9c 6f 25 74 78 13 06 16 |
|
917 |
+ ae e1 29 74 25 66 a2 1a 84 b5 6a 78 c4 74 42 a9 |
|
918 |
+ a4 c4 d2 76 ae 5c b6 54 77 17 9d ea b7 bd 9f bd |
|
919 |
+ 7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5 |
|
920 |
+ 79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15 |
|
921 |
+"""), |
|
922 |
+ expected_signatures={ |
|
923 |
+ SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
924 |
+ signature=bytes.fromhex(""" |
|
925 |
+ 00 00 00 13 65 63 64 |
|
926 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
927 |
+ 00 00 00 68 |
|
928 |
+ 00 00 00 30 |
|
929 |
+ 78 e1 a8 f5 8c d2 7a 21 e5 a2 ca e6 d0 1a 19 f8 |
|
930 |
+ 3a 1c 39 7e 71 a0 e6 7e 93 83 49 95 05 01 d0 3e |
|
931 |
+ 23 22 cd 09 63 7f 7c 6c b0 97 44 6d 7e 48 39 87 |
|
932 |
+ 00 00 00 30 |
|
933 |
+ 10 ee 85 51 77 2b 91 2c e9 42 79 66 59 8a a2 c0 |
|
934 |
+ d2 c8 8a 8f 2f 8f 33 87 9e 12 54 e4 da 02 f9 e7 |
|
935 |
+ 95 f5 82 6f 82 2b 38 6d 6e 5d 17 15 ac 12 e7 62 |
|
936 |
+"""), |
|
937 |
+ derived_passphrase=rb"""AAAAMHjhqPWM0noh5aLK5tAaGfg6HDl+caDmfpODSZUFAdA+IyLNCWN/fGywl0Rtfkg5hwAAADAQ7oVRdyuRLOlCeWZZiqLA0siKjy+PM4eeElTk2gL555X1gm+CKzhtbl0XFawS52I=""", |
|
938 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
939 |
+ ), |
|
940 |
+ SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
941 |
+ signature=bytes.fromhex(""" |
|
942 |
+ 00 00 00 13 65 63 64 |
|
943 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 |
|
944 |
+ 00 00 00 69 |
|
945 |
+ 00 00 00 30 |
|
946 |
+ 4b 3e b7 22 c2 87 77 6d e0 3e f5 05 75 36 b6 0f |
|
947 |
+ cd 9f a4 49 c7 48 ef 76 fd ea 4b 49 e3 b1 f2 22 |
|
948 |
+ d5 41 22 d7 96 b2 29 70 ff bb 81 97 27 e2 35 60 |
|
949 |
+ 00 00 00 31 00 |
|
950 |
+ c8 a4 d8 62 fe f2 a6 63 97 98 08 c7 39 24 b2 55 |
|
951 |
+ 0a b8 e7 79 ab a6 62 96 3e cc ea 73 e2 fb dc 46 |
|
952 |
+ d6 25 b9 c8 0c e8 3e 33 91 51 78 25 a8 c5 46 85 |
|
953 |
+"""), |
|
954 |
+ derived_passphrase=rb"""AAAAMEs+tyLCh3dt4D71BXU2tg/Nn6RJx0jvdv3qS0njsfIi1UEi15ayKXD/u4GXJ+I1YAAAADEAyKTYYv7ypmOXmAjHOSSyVQq453mrpmKWPszqc+L73EbWJbnIDOg+M5FReCWoxUaF""", |
|
955 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
956 |
+ ), |
|
957 |
+ }, |
|
958 |
+ ), |
|
959 |
+ "ecdsa521": SSHTestKey( |
|
960 |
+ private_key=rb"""-----BEGIN OPENSSH PRIVATE KEY----- |
|
961 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS |
|
962 |
+1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQASVOdwDznmlcGqiLvFtYeVtrAEiVz |
|
963 |
+iIfsL7jEM8Utu/m8WSkPFQtjwqdFw+WfZ0mi6qMbEFgi/ELzZSKVteCSbcMAhqAkOMFKiD |
|
964 |
+u4bxvsM6bT02Ru7q2yT41ySyGhUD0QySBnI6Ckt/wnQ1TEpj8zDKiRErxs9e6QLGElNRkz |
|
965 |
+LPMs+mMAAAEY2FXeh9hV3ocAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ |
|
966 |
+AAAIUEAElTncA855pXBqoi7xbWHlbawBIlc4iH7C+4xDPFLbv5vFkpDxULY8KnRcPln2dJ |
|
967 |
+ouqjGxBYIvxC82UilbXgkm3DAIagJDjBSog7uG8b7DOm09Nkbu6tsk+NckshoVA9EMkgZy |
|
968 |
+OgpLf8J0NUxKY/MwyokRK8bPXukCxhJTUZMyzzLPpjAAAAQSFqUmKK7lGQzxT6GKZSLDju |
|
969 |
+U3otwLYnuj+/5AdzuB/zotu95UdFv9I2DNXzd9E4WAyz6IqBBNcsMkxrzHAdqsYDAAAAG3 |
|
970 |
+Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQ== |
|
971 |
+-----END OPENSSH PRIVATE KEY----- |
|
972 |
+""", |
|
973 |
+ private_key_blob=bytes.fromhex(""" |
|
974 |
+ 00 00 00 13 65 63 64 |
|
975 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
976 |
+ 00 00 00 08 6e 69 73 74 70 35 32 31 |
|
977 |
+ 00 00 00 85 04 00 49 53 9d |
|
978 |
+ c0 3c e7 9a 57 06 aa 22 ef 16 d6 1e 56 da c0 12 |
|
979 |
+ 25 73 88 87 ec 2f b8 c4 33 c5 2d bb f9 bc 59 29 |
|
980 |
+ 0f 15 0b 63 c2 a7 45 c3 e5 9f 67 49 a2 ea a3 1b |
|
981 |
+ 10 58 22 fc 42 f3 65 22 95 b5 e0 92 6d c3 00 86 |
|
982 |
+ a0 24 38 c1 4a 88 3b b8 6f 1b ec 33 a6 d3 d3 64 |
|
983 |
+ 6e ee ad b2 4f 8d 72 4b 21 a1 50 3d 10 c9 20 67 |
|
984 |
+ 23 a0 a4 b7 fc 27 43 54 c4 a6 3f 33 0c a8 91 12 |
|
985 |
+ bc 6c f5 ee 90 2c 61 25 35 19 33 2c f3 2c fa 63 |
|
986 |
+ 00 00 00 41 21 |
|
987 |
+ 6a 52 62 8a ee 51 90 cf 14 fa 18 a6 52 2c 38 ee |
|
988 |
+ 53 7a 2d c0 b6 27 ba 3f bf e4 07 73 b8 1f f3 a2 |
|
989 |
+ db bd e5 47 45 bf d2 36 0c d5 f3 77 d1 38 58 0c |
|
990 |
+ b3 e8 8a 81 04 d7 2c 32 4c 6b cc 70 1d aa c6 03 |
|
991 |
+ 00 00 00 1b 74 65 73 74 20 6b 65 79 20 77 69 |
|
992 |
+ 74 68 6f 75 74 20 70 61 73 73 70 68 72 61 73 65 |
|
993 |
+"""), |
|
994 |
+ public_key=rb"""ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABJU53APOeaVwaqIu8W1h5W2sASJXOIh+wvuMQzxS27+bxZKQ8VC2PCp0XD5Z9nSaLqoxsQWCL8QvNlIpW14JJtwwCGoCQ4wUqIO7hvG+wzptPTZG7urbJPjXJLIaFQPRDJIGcjoKS3/CdDVMSmPzMMqJESvGz17pAsYSU1GTMs8yz6Yw== test key without passphrase |
|
995 |
+""", |
|
996 |
+ public_key_data=bytes.fromhex(""" |
|
997 |
+ 00 00 00 13 65 63 64 |
|
998 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
999 |
+ 00 00 00 08 6e 69 73 74 70 35 32 31 |
|
1000 |
+ 00 00 00 85 04 00 49 53 9d |
|
1001 |
+ c0 3c e7 9a 57 06 aa 22 ef 16 d6 1e 56 da c0 12 |
|
1002 |
+ 25 73 88 87 ec 2f b8 c4 33 c5 2d bb f9 bc 59 29 |
|
1003 |
+ 0f 15 0b 63 c2 a7 45 c3 e5 9f 67 49 a2 ea a3 1b |
|
1004 |
+ 10 58 22 fc 42 f3 65 22 95 b5 e0 92 6d c3 00 86 |
|
1005 |
+ a0 24 38 c1 4a 88 3b b8 6f 1b ec 33 a6 d3 d3 64 |
|
1006 |
+ 6e ee ad b2 4f 8d 72 4b 21 a1 50 3d 10 c9 20 67 |
|
1007 |
+ 23 a0 a4 b7 fc 27 43 54 c4 a6 3f 33 0c a8 91 12 |
|
1008 |
+ bc 6c f5 ee 90 2c 61 25 35 19 33 2c f3 2c fa 63 |
|
1009 |
+"""), |
|
1010 |
+ expected_signatures={ |
|
1011 |
+ SSHTestKeyDeterministicSignatureClass.RFC_6979: SSHTestKeyDeterministicSignature( |
|
1012 |
+ signature=bytes.fromhex(""" |
|
1013 |
+ 00 00 00 13 65 63 64 |
|
1014 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1015 |
+ 00 00 00 8b |
|
1016 |
+ 00 00 00 42 01 d8 |
|
1017 |
+ ea c2 1e 55 c6 9e dd 4b 00 ed 1b 93 19 cc 9b 74 |
|
1018 |
+ 27 44 c0 c0 e3 5b 3d 81 15 00 12 cc 07 89 54 97 |
|
1019 |
+ ec 60 42 ad e6 40 c1 c6 5f c0 1b c3 0a 8e 58 6e |
|
1020 |
+ da 3f a9 57 90 04 79 46 1d 48 bb 19 67 e9 65 19 |
|
1021 |
+ 00 00 00 41 7d |
|
1022 |
+ 58 e0 2e d7 86 2e 36 8c 1a 44 23 af 19 e7 51 97 |
|
1023 |
+ bb fb 32 90 a1 35 bb 88 d7 b5 22 37 b3 99 ba e4 |
|
1024 |
+ a7 9d 2d 56 14 0a f5 68 f5 cc 38 84 e9 b6 c6 71 |
|
1025 |
+ 7a 3b 87 e7 7a b1 37 e7 1d e6 80 96 d1 a6 1e bc |
|
1026 |
+"""), |
|
1027 |
+ derived_passphrase=rb"""AAAAQgHY6sIeVcae3UsA7RuTGcybdCdEwMDjWz2BFQASzAeJVJfsYEKt5kDBxl/AG8MKjlhu2j+pV5AEeUYdSLsZZ+llGQAAAEF9WOAu14YuNowaRCOvGedRl7v7MpChNbuI17UiN7OZuuSnnS1WFAr1aPXMOITptsZxejuH53qxN+cd5oCW0aYevA==""", |
|
1028 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.RFC_6979, |
|
1029 |
+ ), |
|
1030 |
+ SSHTestKeyDeterministicSignatureClass.Pageant_068_080: SSHTestKeyDeterministicSignature( |
|
1031 |
+ signature=bytes.fromhex(""" |
|
1032 |
+ 00 00 00 13 65 63 64 |
|
1033 |
+ 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 |
|
1034 |
+ 00 00 00 8c |
|
1035 |
+ 00 00 00 42 01 ce |
|
1036 |
+ fe 9d 66 b6 01 76 2e 86 c2 ab 68 62 73 44 05 23 |
|
1037 |
+ fd d1 79 07 fc 45 f5 c0 83 36 88 61 d4 04 79 90 |
|
1038 |
+ b0 ef 8b 3c b5 55 0e cc 26 6b a0 3e 6a 04 48 ca |
|
1039 |
+ e4 6a a5 a0 cf 91 5f 71 6f 37 9a 0f 6b a9 fb 9b |
|
1040 |
+ 00 00 00 42 01 6d |
|
1041 |
+ 21 77 c6 13 fa ea ac de 90 19 24 5a d2 61 39 d9 |
|
1042 |
+ 66 9b 86 1a 41 04 58 a2 9b b8 93 b6 6f 82 23 f2 |
|
1043 |
+ 01 23 c7 ff 5a d3 86 95 0f da 28 f9 3b e3 9c 27 |
|
1044 |
+ e7 b2 d7 66 4e 5f 38 36 4c 8c be 76 4e fa 0a 2d |
|
1045 |
+"""), |
|
1046 |
+ derived_passphrase=rb"""AAAAQgHO/p1mtgF2LobCq2hic0QFI/3ReQf8RfXAgzaIYdQEeZCw74s8tVUOzCZroD5qBEjK5GqloM+RX3FvN5oPa6n7mwAAAEIBbSF3xhP66qzekBkkWtJhOdlmm4YaQQRYopu4k7ZvgiPyASPH/1rThpUP2ij5O+OcJ+ey12ZOXzg2TIy+dk76Ci0=""", |
|
1047 |
+ signature_class=SSHTestKeyDeterministicSignatureClass.Pageant_068_080, |
|
1048 |
+ ), |
|
1049 |
+ }, |
|
1050 |
+ ), |
|
1051 |
+} |
|
1052 |
+"""The master list of SSH test keys.""" |
|
1053 |
+SUPPORTED_KEYS: Mapping[str, SSHTestKey] = { |
|
1054 |
+ k: v for k, v in ALL_KEYS.items() if v.is_suitable() |
|
1055 |
+} |
|
1056 |
+"""The subset of SSH test keys suitable for use with vault.""" |
|
1057 |
+UNSUITABLE_KEYS: Mapping[str, SSHTestKey] = { |
|
1058 |
+ k: v for k, v in ALL_KEYS.items() if not v.is_suitable() |
|
1059 |
+} |
|
1060 |
+"""The subset of SSH test keys not suitable for use with vault.""" |
|
1061 |
+ |
|
1062 |
+ |
|
1063 |
+# Vault test configurations |
|
1064 |
+# ------------------------- |
|
1065 |
+ |
|
1066 |
+ |
|
1067 |
+TEST_CONFIGS: list[VaultTestConfig] = [ |
|
1068 |
+ VaultTestConfig(None, "not a dict", None), |
|
1069 |
+ VaultTestConfig({}, "missing required keys", None), |
|
1070 |
+ VaultTestConfig( |
|
1071 |
+ {"global": None, "services": {}}, "bad config value: global", None |
|
1072 |
+ ), |
|
1073 |
+ VaultTestConfig( |
|
1074 |
+ {"global": {"key": 123}, "services": {}}, |
|
1075 |
+ "bad config value: global.key", |
|
1076 |
+ None, |
|
1077 |
+ ), |
|
1078 |
+ VaultTestConfig( |
|
1079 |
+ {"global": {"phrase": "abc", "key": "..."}, "services": {}}, |
|
1080 |
+ "", |
|
1081 |
+ None, |
|
1082 |
+ ), |
|
1083 |
+ VaultTestConfig({"services": None}, "bad config value: services", None), |
|
1084 |
+ VaultTestConfig( |
|
1085 |
+ {"services": {"1": {}, 2: {}}}, 'bad config value: services."2"', None |
|
1086 |
+ ), |
|
1087 |
+ VaultTestConfig( |
|
1088 |
+ {"services": {"1": {}, "2": 2}}, 'bad config value: services."2"', None |
|
1089 |
+ ), |
|
1090 |
+ VaultTestConfig( |
|
1091 |
+ {"services": {"sv": {"notes": ["sentinel", "list"]}}}, |
|
1092 |
+ "bad config value: services.sv.notes", |
|
1093 |
+ None, |
|
1094 |
+ ), |
|
1095 |
+ VaultTestConfig( |
|
1096 |
+ {"services": {"sv": {"notes": "blah blah blah"}}}, "", None |
|
1097 |
+ ), |
|
1098 |
+ VaultTestConfig( |
|
1099 |
+ {"services": {"sv": {"length": "200"}}}, |
|
1100 |
+ "bad config value: services.sv.length", |
|
1101 |
+ None, |
|
1102 |
+ ), |
|
1103 |
+ VaultTestConfig( |
|
1104 |
+ {"services": {"sv": {"length": 0.5}}}, |
|
1105 |
+ "bad config value: services.sv.length", |
|
1106 |
+ None, |
|
1107 |
+ ), |
|
1108 |
+ VaultTestConfig( |
|
1109 |
+ {"services": {"sv": {"length": ["sentinel", "list"]}}}, |
|
1110 |
+ "bad config value: services.sv.length", |
|
1111 |
+ None, |
|
1112 |
+ ), |
|
1113 |
+ VaultTestConfig( |
|
1114 |
+ {"services": {"sv": {"length": -10}}}, |
|
1115 |
+ "bad config value: services.sv.length", |
|
1116 |
+ None, |
|
1117 |
+ ), |
|
1118 |
+ VaultTestConfig( |
|
1119 |
+ {"services": {"sv": {"lower": "10"}}}, |
|
1120 |
+ "bad config value: services.sv.lower", |
|
1121 |
+ None, |
|
1122 |
+ ), |
|
1123 |
+ VaultTestConfig( |
|
1124 |
+ {"services": {"sv": {"upper": -10}}}, |
|
1125 |
+ "bad config value: services.sv.upper", |
|
1126 |
+ None, |
|
1127 |
+ ), |
|
1128 |
+ VaultTestConfig( |
|
1129 |
+ {"services": {"sv": {"number": ["sentinel", "list"]}}}, |
|
1130 |
+ "bad config value: services.sv.number", |
|
1131 |
+ None, |
|
1132 |
+ ), |
|
1133 |
+ VaultTestConfig( |
|
1134 |
+ { |
|
1135 |
+ "global": {"phrase": "my secret phrase"}, |
|
1136 |
+ "services": {"sv": {"length": 10}}, |
|
1137 |
+ }, |
|
1138 |
+ "", |
|
1139 |
+ None, |
|
1140 |
+ ), |
|
1141 |
+ VaultTestConfig( |
|
1142 |
+ {"services": {"sv": {"length": 10, "phrase": "..."}}}, "", None |
|
1143 |
+ ), |
|
1144 |
+ VaultTestConfig( |
|
1145 |
+ {"services": {"sv": {"length": 10, "key": "..."}}}, "", None |
|
1146 |
+ ), |
|
1147 |
+ VaultTestConfig( |
|
1148 |
+ {"services": {"sv": {"upper": 10, "key": "..."}}}, "", None |
|
1149 |
+ ), |
|
1150 |
+ VaultTestConfig( |
|
1151 |
+ {"services": {"sv": {"phrase": "abc", "key": "..."}}}, "", None |
|
1152 |
+ ), |
|
1153 |
+ VaultTestConfig( |
|
1154 |
+ { |
|
1155 |
+ "global": {"phrase": "abc"}, |
|
1156 |
+ "services": {"sv": {"phrase": "abc", "length": 10}}, |
|
1157 |
+ }, |
|
1158 |
+ "", |
|
1159 |
+ None, |
|
1160 |
+ ), |
|
1161 |
+ VaultTestConfig( |
|
1162 |
+ { |
|
1163 |
+ "global": {"key": "..."}, |
|
1164 |
+ "services": {"sv": {"phrase": "abc", "length": 10}}, |
|
1165 |
+ }, |
|
1166 |
+ "", |
|
1167 |
+ None, |
|
1168 |
+ ), |
|
1169 |
+ VaultTestConfig( |
|
1170 |
+ { |
|
1171 |
+ "global": {"key": "..."}, |
|
1172 |
+ "services": {"sv": {"phrase": "abc", "key": "...", "length": 10}}, |
|
1173 |
+ }, |
|
1174 |
+ "", |
|
1175 |
+ None, |
|
1176 |
+ ), |
|
1177 |
+ VaultTestConfig( |
|
1178 |
+ { |
|
1179 |
+ "global": {"key": "..."}, |
|
1180 |
+ "services": { |
|
1181 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1182 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1183 |
+ }, |
|
1184 |
+ }, |
|
1185 |
+ "", |
|
1186 |
+ None, |
|
1187 |
+ ), |
|
1188 |
+ VaultTestConfig( |
|
1189 |
+ { |
|
1190 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1191 |
+ "services": { |
|
1192 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1193 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1194 |
+ }, |
|
1195 |
+ }, |
|
1196 |
+ "", |
|
1197 |
+ None, |
|
1198 |
+ ), |
|
1199 |
+ VaultTestConfig( |
|
1200 |
+ { |
|
1201 |
+ "global": {"key": "...", "unicode_normalization_form": True}, |
|
1202 |
+ "services": {}, |
|
1203 |
+ }, |
|
1204 |
+ "bad config value: global.unicode_normalization_form", |
|
1205 |
+ None, |
|
1206 |
+ ), |
|
1207 |
+ VaultTestConfig( |
|
1208 |
+ { |
|
1209 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1210 |
+ "services": { |
|
1211 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1212 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1213 |
+ }, |
|
1214 |
+ }, |
|
1215 |
+ "", |
|
1216 |
+ ValidationSettings(True), |
|
1217 |
+ ), |
|
1218 |
+ VaultTestConfig( |
|
1219 |
+ { |
|
1220 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1221 |
+ "services": { |
|
1222 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1223 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1224 |
+ }, |
|
1225 |
+ }, |
|
1226 |
+ "extension/unknown key: .global.unicode_normalization_form", |
|
1227 |
+ ValidationSettings(False), |
|
1228 |
+ ), |
|
1229 |
+ VaultTestConfig( |
|
1230 |
+ { |
|
1231 |
+ "global": {"key": "...", "unknown_key": True}, |
|
1232 |
+ "services": { |
|
1233 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1234 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1235 |
+ }, |
|
1236 |
+ }, |
|
1237 |
+ "", |
|
1238 |
+ ValidationSettings(True), |
|
1239 |
+ ), |
|
1240 |
+ VaultTestConfig( |
|
1241 |
+ { |
|
1242 |
+ "global": {"key": "...", "unknown_key": True}, |
|
1243 |
+ "services": { |
|
1244 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1245 |
+ "sv2": {"length": 10, "repeat": 1, "lower": 1}, |
|
1246 |
+ }, |
|
1247 |
+ }, |
|
1248 |
+ "unknown key: .global.unknown_key", |
|
1249 |
+ ValidationSettings(False), |
|
1250 |
+ ), |
|
1251 |
+ VaultTestConfig( |
|
1252 |
+ { |
|
1253 |
+ "global": {"key": "..."}, |
|
1254 |
+ "services": { |
|
1255 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1256 |
+ "sv2": { |
|
1257 |
+ "length": 10, |
|
1258 |
+ "repeat": 1, |
|
1259 |
+ "lower": 1, |
|
1260 |
+ "unknown_key": True, |
|
1261 |
+ }, |
|
1262 |
+ }, |
|
1263 |
+ }, |
|
1264 |
+ "unknown key: .services.sv2.unknown_key", |
|
1265 |
+ ValidationSettings(False), |
|
1266 |
+ ), |
|
1267 |
+ VaultTestConfig( |
|
1268 |
+ { |
|
1269 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1270 |
+ "services": { |
|
1271 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1272 |
+ "sv2": { |
|
1273 |
+ "length": 10, |
|
1274 |
+ "repeat": 1, |
|
1275 |
+ "lower": 1, |
|
1276 |
+ "unknown_key": True, |
|
1277 |
+ }, |
|
1278 |
+ }, |
|
1279 |
+ }, |
|
1280 |
+ "", |
|
1281 |
+ ValidationSettings(True), |
|
1282 |
+ ), |
|
1283 |
+ VaultTestConfig( |
|
1284 |
+ { |
|
1285 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1286 |
+ "services": { |
|
1287 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1288 |
+ "sv2": { |
|
1289 |
+ "length": 10, |
|
1290 |
+ "repeat": 1, |
|
1291 |
+ "lower": 1, |
|
1292 |
+ "unknown_key": True, |
|
1293 |
+ }, |
|
1294 |
+ }, |
|
1295 |
+ }, |
|
1296 |
+ "", |
|
1297 |
+ ValidationSettings(True), |
|
1298 |
+ ), |
|
1299 |
+ VaultTestConfig( |
|
1300 |
+ { |
|
1301 |
+ "global": {"key": "...", "unicode_normalization_form": "NFC"}, |
|
1302 |
+ "services": { |
|
1303 |
+ "sv1": {"phrase": "abc", "length": 10, "upper": 1}, |
|
1304 |
+ "sv2": { |
|
1305 |
+ "length": 10, |
|
1306 |
+ "repeat": 1, |
|
1307 |
+ "lower": 1, |
|
1308 |
+ "unknown_key": True, |
|
1309 |
+ }, |
|
1310 |
+ }, |
|
1311 |
+ }, |
|
1312 |
+ "", |
|
1313 |
+ ValidationSettings(True), |
|
1314 |
+ ), |
|
1315 |
+] |
|
1316 |
+"""The master list of test configurations for vault.""" |
|
1317 |
+ |
|
1318 |
+ |
|
1319 |
+# Common vault sample settings, passphrases, results, etc. |
|
1320 |
+# -------------------------------------------------------- |
|
1321 |
+ |
|
1322 |
+ |
|
1323 |
+DUMMY_SERVICE = "service1" |
|
1324 |
+"""A standard/sample service name.""" |
|
1325 |
+DUMMY_PASSPHRASE = "my secret passphrase" |
|
1326 |
+"""A standard/sample passphrase.""" |
|
1327 |
+DUMMY_KEY1 = SUPPORTED_KEYS["ed25519"].public_key_data |
|
1328 |
+"""A sample universally supported SSH test key (in wire format).""" |
|
1329 |
+DUMMY_KEY1_B64 = base64.standard_b64encode(DUMMY_KEY1).decode("ASCII") |
|
1330 |
+""" |
|
1331 |
+A sample universally supported SSH test key (in `authorized_keys` format). |
|
1332 |
+""" |
|
1333 |
+DUMMY_KEY2 = SUPPORTED_KEYS["rsa"].public_key_data |
|
1334 |
+"""A second supported SSH test key (in wire format).""" |
|
1335 |
+DUMMY_KEY2_B64 = base64.standard_b64encode(DUMMY_KEY2).decode("ASCII") |
|
1336 |
+"""A second supported SSH test key (in `authorized_keys` format).""" |
|
1337 |
+DUMMY_KEY3 = SUPPORTED_KEYS["ed448"].public_key_data |
|
1338 |
+"""A third supported SSH test key (in wire format).""" |
|
1339 |
+DUMMY_KEY3_B64 = base64.standard_b64encode(DUMMY_KEY3).decode("ASCII") |
|
1340 |
+"""A third supported SSH test key (in `authorized_keys` format).""" |
|
1341 |
+DUMMY_CONFIG_SETTINGS = { |
|
1342 |
+ "length": 10, |
|
1343 |
+ "upper": 1, |
|
1344 |
+ "lower": 1, |
|
1345 |
+ "repeat": 5, |
|
1346 |
+ "number": 1, |
|
1347 |
+ "space": 1, |
|
1348 |
+ "dash": 1, |
|
1349 |
+ "symbol": 1, |
|
1350 |
+} |
|
1351 |
+"""Sample vault settings.""" |
|
1352 |
+DUMMY_RESULT_PASSPHRASE = b".2V_QJkd o" |
|
1353 |
+""" |
|
1354 |
+The passphrase derived from [`DUMMY_SERVICE`][] using [`DUMMY_PASSPHRASE`][]. |
|
1355 |
+""" |
|
1356 |
+DUMMY_RESULT_KEY1 = b"E<b<{ -7iG" |
|
1357 |
+""" |
|
1358 |
+The passphrase derived from [`DUMMY_SERVICE`][] using [`DUMMY_KEY1`][]. |
|
1359 |
+""" |
|
1360 |
+DUMMY_PHRASE_FROM_KEY1_RAW = ( |
|
1361 |
+ b"\x00\x00\x00\x0bssh-ed25519" |
|
1362 |
+ b"\x00\x00\x00@\xf0\x98\x19\x80l\x1a\x97\xd5&\x03n" |
|
1363 |
+ b"\xcc\xe3e\x8f\x86f\x07\x13\x19\x13\t!33\xf9\xe46S" |
|
1364 |
+ b"\x1d\xaf\xfd\r\x08\x1f\xec\xf8s\x9b\x8c_U9\x16|ST," |
|
1365 |
+ b"\x1eR\xbb0\xed\x7f\x89\xe2/iQU\xd8\x9e\xa6\x02" |
|
1366 |
+) |
|
1367 |
+""" |
|
1368 |
+The "equivalent master passphrase" derived from [`DUMMY_KEY1`][] (raw format). |
|
1369 |
+""" |
|
1370 |
+DUMMY_PHRASE_FROM_KEY1 = b"8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==" |
|
1371 |
+""" |
|
1372 |
+The "equivalent master passphrase" derived from [`DUMMY_KEY1`][] (in base64). |
|
1373 |
+""" |
|
1374 |
+ |
|
1375 |
+ |
|
1376 |
+# Vault native configuration storage data |
|
1377 |
+# --------------------------------------- |
|
1378 |
+ |
|
1379 |
+ |
|
1380 |
+VAULT_MASTER_KEY = "vault key" |
|
1381 |
+""" |
|
1382 |
+The storage passphrase used to encrypt all sample vault native configurations. |
|
1383 |
+""" |
|
1384 |
+VAULT_V02_CONFIG = "P7xeh5y4jmjpJ2pFq4KUcTVoaE9ZOEkwWmpVTURSSWQxbGt6emN4aFE4eFM3anVPbDRNTGpOLzY3eDF5aE1YTm5LNWh5Q1BwWTMwM3M5S083MWRWRFlmOXNqSFJNcStGMWFOS3c2emhiOUNNenZYTmNNMnZxaUErdlRoOGF2ZHdGT1ZLNTNLOVJQcU9jWmJrR3g5N09VcVBRZ0ZnSFNUQy9HdFVWWnFteVhRVkY3MHNBdnF2ZWFEbFBseWRGelE1c3BFTnVUckRQdWJSL29wNjFxd2Y2ZVpob3VyVzRod3FKTElTenJ1WTZacTJFOFBtK3BnVzh0QWVxcWtyWFdXOXYyenNQeFNZbWt1MDU2Vm1kVGtISWIxWTBpcWRFbyswUVJudVVhZkVlNVpGWDA4WUQ2Q2JTWW81SnlhQ2Zxa3cxNmZoQjJES0Uyd29rNXpSck5iWVBrVmEwOXFya1NpMi9saU5LL3F0M3N3MjZKekNCem9ER2svWkZ0SUJLdmlHRno0VlQzQ3pqZTBWcTM3YmRiNmJjTkhqUHZoQ0NxMW1ldW1XOFVVK3pQMEtUMkRMVGNvNHFlOG40ck5KcGhsYXg1b1VzZ1NYU1B2T3RXdEkwYzg4NWE3YWUzOWI1MDI0MThhMWZjODQ3MDA2OTJmNDQ0MDkxNGFiNmRlMGQ2YjZiNjI5NGMwN2IwMmI4MGZi" |
|
1385 |
+""" |
|
1386 |
+A sample vault native configuration, in v0.2 format, encoded in base64 |
|
1387 |
+and encrypted with [`VAULT_MASTER_KEY`][]. |
|
1388 |
+""" |
|
1389 |
+VAULT_V02_CONFIG_DATA = { |
|
1390 |
+ "global": { |
|
1391 |
+ "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1392 |
+ }, |
|
1393 |
+ "services": { |
|
1394 |
+ "(meta)": { |
|
1395 |
+ "notes": "This config was originally in v0.2 format.", |
|
1396 |
+ }, |
|
1397 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1398 |
+ }, |
|
1399 |
+} |
|
1400 |
+""" |
|
1401 |
+The plaintext contents (a vault native configuration) stored in |
|
1402 |
+[`VAULT_V02_CONFIG`][]. |
|
1403 |
+""" |
|
1404 |
+VAULT_V03_CONFIG = "sBPBrr8BFHPxSJkV/A53zk9zwDQHFxLe6UIusCVvzFQre103pcj5xxmE11lMTA0U2QTYjkhRXKkH5WegSmYpAnzReuRsYZlWWp6N4kkubf+twZ9C3EeggPm7as2Af4TICHVbX4uXpIHeQJf9y1OtqrO+SRBrgPBzgItoxsIxebxVKgyvh1CZQOSkn7BIzt9xKhDng3ubS4hQ91fB0QCumlldTbUl8tj4Xs5JbvsSlUMxRlVzZ0OgAOrSsoWELXmsp6zXFa9K6wIuZa4wQuMLQFHiA64JO1CR3I+rviWCeMlbTOuJNx6vMB5zotKJqA2hIUpN467TQ9vI4g/QTo40m5LT2EQKbIdTvBQAzcV4lOcpr5Lqt4LHED5mKvm/4YfpuuT3I3XCdWfdG5SB7ciiB4Go+xQdddy3zZMiwm1fEwIB8XjFf2cxoJdccLQ2yxf+9diedBP04EsMHrvxKDhQ7/vHl7xF2MMFTDKl3WFd23vvcjpR1JgNAKYprG/e1p/7" |
|
1405 |
+""" |
|
1406 |
+A sample vault native configuration, in v0.3 format, encoded in base64 |
|
1407 |
+and encrypted with [`VAULT_MASTER_KEY`][]. |
|
1408 |
+""" |
|
1409 |
+VAULT_V03_CONFIG_DATA = { |
|
1410 |
+ "global": { |
|
1411 |
+ "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1412 |
+ }, |
|
1413 |
+ "services": { |
|
1414 |
+ "(meta)": { |
|
1415 |
+ "notes": "This config was originally in v0.3 format.", |
|
1416 |
+ }, |
|
1417 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1418 |
+ }, |
|
1419 |
+} |
|
1420 |
+""" |
|
1421 |
+The plaintext contents (a vault native configuration) stored in |
|
1422 |
+[`VAULT_V03_CONFIG`][]. |
|
1423 |
+""" |
|
1424 |
+VAULT_STOREROOM_CONFIG_ZIPPED = b""" |
|
1425 |
+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 |
|
1475 |
+""" |
|
1476 |
+""" |
|
1477 |
+A sample vault native configuration, in storeroom format, encrypted with |
|
1478 |
+[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1479 |
+and then encoded in base64. |
|
1480 |
+""" |
|
1481 |
+VAULT_STOREROOM_CONFIG_DATA = { |
|
1482 |
+ "global": { |
|
1483 |
+ "phrase": DUMMY_PASSPHRASE.rstrip("\n"), |
|
1484 |
+ }, |
|
1485 |
+ "services": { |
|
1486 |
+ "(meta)": { |
|
1487 |
+ "notes": "This config was originally in storeroom format.", |
|
1488 |
+ }, |
|
1489 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
1490 |
+ }, |
|
1491 |
+} |
|
1492 |
+""" |
|
1493 |
+The parsed vault configuration stored in |
|
1494 |
+[`VAULT_STOREROOM_CONFIG_ZIPPED`][]. |
|
1495 |
+""" |
|
1496 |
+ |
|
1497 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED_JAVASCRIPT_SOURCE = """ |
|
1498 |
+// Executed in the top-level directory of the vault project code, in Node.js. |
|
1499 |
+const storeroom = require('storeroom') |
|
1500 |
+const Store = require('./lib/store.js') |
|
1501 |
+let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1502 |
+await store._storeroom.put('/services/array/', ['entry1','entry2']) |
|
1503 |
+// The resulting "broken-dir" was then zipped manually. |
|
1504 |
+""" |
|
1505 |
+""" |
|
1506 |
+The JavaScript source for the script that generated the storeroom |
|
1507 |
+archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED`][]. |
|
1508 |
+""" |
|
1509 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED = b""" |
|
1510 |
+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 |
|
1544 |
+""" |
|
1545 |
+""" |
|
1546 |
+A sample corrupted storeroom archive, encrypted with |
|
1547 |
+[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1548 |
+and then encoded in base64. |
|
1549 |
+ |
|
1550 |
+The archive contains a directory `/services/array/` that claims to have |
|
1551 |
+two child items 'entry1' and 'entry2', but no such child items are |
|
1552 |
+present in the archive. See |
|
1553 |
+[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED_JAVASCRIPT_SOURCE`][] for |
|
1554 |
+the exact script that created this archive. |
|
1555 |
+""" |
|
1556 |
+ |
|
1557 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2_JAVASCRIPT_SOURCE = """ |
|
1558 |
+// Executed in the top-level directory of the vault project code, in Node.js. |
|
1559 |
+const storeroom = require('storeroom') |
|
1560 |
+const Store = require('./lib/store.js') |
|
1561 |
+let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1562 |
+await store._storeroom.put('/services/array/', 'not a directory index') |
|
1563 |
+// The resulting "broken-dir" was then zipped manually. |
|
1564 |
+""" |
|
1565 |
+""" |
|
1566 |
+The JavaScript source for the script that generated the storeroom |
|
1567 |
+archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2`][]. |
|
1568 |
+""" |
|
1569 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2 = b""" |
|
1570 |
+UEsDBAoAAAAAAM6NSVmrcHdV5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV3ZS9LZkJp |
|
1571 |
+L0V0OUcrZmxYM3gxaFU4ZjE4YlE3S253bHoxN0IxSDE3cUhVOGdWK2RpWWY5MTdFZ0YrSStidEpZ |
|
1572 |
+VXBzWVZVck45OC9uLzdsZnl2NUdGVEg2NWZxVy93YjlOc2MxeEZ4ck43Q3p4eTZ5MVAxZzFPb2VK |
|
1573 |
+b0RZU3J6YXlwT0E2M3pidmk0ZTRiREMyNXhPTXl5NHBoMDFGeGdnQmpSNnpUcmR2UDk2UlZQd0I5 |
|
1574 |
+WitOZkZWZUlXT1NQN254ZFNYMGdFbkZ4SDBmWDkzNTFaTTZnPVBLAwQKAAAAAADOjUlZJg3/BhcC |
|
1575 |
+AAAXAgAAAgAAADBieyJ2ZXJzaW9uIjoxfQpBVXJJMjNDQ2VpcW14cUZRMlV4SUpBaUoxNEtyUzh2 |
|
1576 |
+SXpIa2xROURBaFRlVHNFMmxPVUg4WUhTcUk1cXRGSHBqY3c1WkRkZmRtUlEwQXVGRjllY3lkam14 |
|
1577 |
+dDdUemRYLzNmNFUvTGlVV2dLRmQ1K1FEN3BlVlE1bWpqeHNlUEpHTDlhTWlKaGxSUVB4SmtUbjBx |
|
1578 |
+U2poM1RUT0ZZbVAzV0JkdlUyWnF2RzhaSDk2cU1WcnZsQ0dMRmZTc2svVXlvcHZKdENONUVXcTRZ |
|
1579 |
+SDUwNFNiejFIUVhWd2RjejlrS1BuR3J6SVA4ZmZtZnhXQ0U0TmtLb0ZPQXZuNkZvS3FZdGlGbFE9 |
|
1580 |
+PQpBVXBMUVMrMG9VeEZTeCtxbTB3SUtyM1MvTVJxYWJJTFlEUnY0aHlBMVE2TGR2Nlk0UmJ0enVz |
|
1581 |
+NzRBc0cxbVhhenlRU2hlZVowdk0xM2ZyTFA4YlV0VHBaRyszNXF1eUhLM2NaWVJRZUxKM0JzejZz |
|
1582 |
+b0xaQjNZTkpNenFxTTQrdzM1U0FZZ2lMU1NkN05NeWVrTHNhRUIzRDFOajlTRk85K3NGNEpFMWVL |
|
1583 |
+UXpNMkltNk9qOUNVQjZUSTV3UitibksxN1BnY2RaeTZUMVRMWElVREVxcDg4dWdsWmRFTVcrNU9k |
|
1584 |
+aE5ZbXEzZERWVWV4UnJpM1AwUmVBSi9KMGdJNkNoUUE9PVBLAwQKAAAAAADOjUlZTNfdphcCAAAX |
|
1585 |
+AgAAAgAAADBmeyJ2ZXJzaW9uIjoxfQpBWVJqOVpIUktGUEVKOHM2YVY2TkRoTk5jQlZ5cGVYUmdz |
|
1586 |
+cnBldFQ0cGhJRGROWFdGYzRia0daYkJxMngwRDFkcVNjYWk5UzEveDZ2K28zRE0rVEF2OVE3ZFVR |
|
1587 |
+QWVKR3RmRkhJZDZxWW0ybEdNSnF5WTRNWm14aE9YdXliend0V3Q4Mnhvb041QTZNcWpINmxKQllD |
|
1588 |
+UUN3ZEJjb3RER0EwRnlnVTEzeHV2WnIzT1puZnFFRGRqbzMxNkw5aExDN1RxMTYwUHpBOXJOSDMz |
|
1589 |
+ZkNBcUhIVXZiYlFQQWErekw1d3dEN3FlWkY2MHdJaEwvRmk5L3JhNGJDcHZRNC9ORWpRd3c9PQpB |
|
1590 |
+WWNGUDB1Y2xMMHh3ZDM2UXZXbm4wWXFsOU5WV0s3c05CMTdjdmM3N3VDZ0J2OE9XYkR5UHk5d05h |
|
1591 |
+R2NQQzdzcVdZdHpZRlBHR0taVjhVUzA1YTVsV1BabDNGVFNuQXNtekxPelBlcFZxaitleDU3aEsx |
|
1592 |
+QnV1bHkrUCtYQkE0YUtsaDM3c0RJL3I0UE1BVlJuMDNoSDJ5dEhDMW9PbjF0V1M5Q1NLV1pSMThh |
|
1593 |
+djdTT0RBMVBNRnFYTmZKZVNTaVJiQ2htbDdOcFVLbjlXSGJZandybDlqN0JSdy9kWjhNQldCb3Ns |
|
1594 |
+Nlc1dGZtdnJMVHhGRFBXYUgzSUp0T0czMEI1M3c9PVBLAwQKAAAAAADOjUlZn9rNID8CAAA/AgAA |
|
1595 |
+AgAAADFkeyJ2ZXJzaW9uIjoxfQpBYWFBb3lqaGljVDZ4eXh1c0U0RVlDZCtxbE81Z0dEYTBNSFVS |
|
1596 |
+MmgrSW9QMHV4UkY3b1BRS2czOHlQUEN3Ny9MYVJLQ0dQZ0RyZ2RpTWJTeUwzZ3ZNMFhseVpVMVBW |
|
1597 |
+QVJvNEFETU9lbXgrOWhtS0hjQWNKMG5EeW5oSkhGYTYyb2xyQUNxekZzblhKNVBSeEVTVzVEbUh0 |
|
1598 |
+Ui9nRm5Wa1FvalhyVW4ybmpYMjVVanZQaXhlMU96Y0daMmQ0MjdVTGdnY1hqMkhSdjJiZldDNDUw |
|
1599 |
+SGFXS3FDckZlYWlrQ2xkUUM2WGV3SkxZUjdvQUY3UjVha2ttK3M2MXNCRTVCaTg0QmJLWHluc1NG |
|
1600 |
+ejE0TXFrd2JMK1VMYVk9CkFUT3dqTUFpa3Q4My9NTW5KRXQ2b3EyNFN4KzJKNDc2K2gyTmEzbHUr |
|
1601 |
+MDg0cjlBT25aaUk0TmlYV0N1Q0lzakEzcTBwUHFJS1VXZHlPQW9uM2VHY0huZUppWUtVYllBaUJI |
|
1602 |
+MVNmbnhQQkMzZkFMRklybkQ4Y0VqeGpPcUFUaTQ5dE1mRmtib0dNQ3dEdFY0V3NJL0tLUlRCOFd1 |
|
1603 |
+MnNXK2J0V3QzVWlvZG9ZeUVLTDk3ekNNemZqdGptejF4SDhHTXY5WDVnaG9NSW5RQVNvYlRreVZ4 |
|
1604 |
+dWo5YnlDazdNbU0vK21ZL3AwZE9oYVY0Nncwcm04UGlvWEtzdzR4bXB3ditDWC9PRXV3Uy9meDJT |
|
1605 |
+Y0lOQnNuYVRiWT1QSwECHgMKAAAAAADOjUlZq3B3VeYAAADmAAAABQAAAAAAAAAAAAAApIEAAAAA |
|
1606 |
+LmtleXNQSwECHgMKAAAAAADOjUlZJg3/BhcCAAAXAgAAAgAAAAAAAAAAAAAApIEJAQAAMGJQSwEC |
|
1607 |
+HgMKAAAAAADOjUlZTNfdphcCAAAXAgAAAgAAAAAAAAAAAAAApIFAAwAAMGZQSwECHgMKAAAAAADO |
|
1608 |
+jUlZn9rNID8CAAA/AgAAAgAAAAAAAAAAAAAApIF3BQAAMWRQSwUGAAAAAAQABADDAAAA1gcAAAAA |
|
1609 |
+""" |
|
1610 |
+""" |
|
1611 |
+A sample corrupted storeroom archive, encrypted with |
|
1612 |
+[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1613 |
+and then encoded in base64. |
|
1614 |
+ |
|
1615 |
+The archive contains a directory `/services/array/` whose list of child |
|
1616 |
+items does not adhere to the serialization format. See |
|
1617 |
+[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2_JAVASCRIPT_SOURCE`][] for |
|
1618 |
+the exact script that created this archive. |
|
1619 |
+""" |
|
1620 |
+ |
|
1621 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3_JAVASCRIPT_SOURCE = """ |
|
1622 |
+// Executed in the top-level directory of the vault project code, in Node.js. |
|
1623 |
+const storeroom = require('storeroom') |
|
1624 |
+const Store = require('./lib/store.js') |
|
1625 |
+let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1626 |
+await store._storeroom.put('/services/array/', [null, 1, true, [], {}]) |
|
1627 |
+// The resulting "broken-dir" was then zipped manually. |
|
1628 |
+""" |
|
1629 |
+""" |
|
1630 |
+The JavaScript source for the script that generated the storeroom |
|
1631 |
+archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3`][]. |
|
1632 |
+""" |
|
1633 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3 = b""" |
|
1634 |
+UEsDBAoAAAAAAEOPSVnVlcff5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV4dVBHUDBi |
|
1635 |
+YkxrUVdvWnV5ZUJQRy8xdmM2MCt6MThOa3BsS09ydFAvUTVnQmxkYVpIOG10dTE5VWZFNGdGRGRj |
|
1636 |
+eHJtWUd4eXZDZFNqcVlOaDh4cTlzM3VydkdRTWFwcnhtdlZGZUxoSW4zZnVlTDAweEk0ZmlLenZN |
|
1637 |
+MmthUlRsNWNORGh3eUNlWVk4dzhBcXNhYjNyVWVsOEE0eVQ0cHU2d2tmQ3dTWUdqeG5HR29EcWJK |
|
1638 |
+VnVJVWNpZVBEcU9PTzU2b0MyMG9lT01adFVkTUtxV28zYnFZPVBLAwQKAAAAAABDj0lZ77OVHxcC |
|
1639 |
+AAAXAgAAAgAAADBjeyJ2ZXJzaW9uIjoxfQpBZllFQVVobEkyU2lZeGlrdWh0RzRNbUN3L1V2THBN |
|
1640 |
+VVhwVlB0NlRwdzRyNGdocVJhbGZWZ0hxUHFtbTczSnltdFFrNnZnR2JRdUpiQmVlYjYwOHNrMGk4 |
|
1641 |
+ZFJVZjNwdlc2SnUyejljQkdwOG5mTFpTdlNad1lLN09UK2gzSDNDcmoxbXNicEZUcHVldW81NXc1 |
|
1642 |
+dGdYMnBuWXNWTVcrczdjaHEyMUIya2lIVEZrdGt1MXlaRzhPYkVUQjNCOFNGODVVbi9CUjFEMHJ1 |
|
1643 |
+ME9zOWl4ZWM2VmNTMitTZndtNnNtSlk2ZW9ZNTJzOGJNRGdYMndjQ0srREdkOEo2VWp0NG5OQVE9 |
|
1644 |
+PQpBUWlPRnRZcmJybWUycEwxRFpGT1BjU0RHOUN2cVkvbHhTWGIwaVJUdmtIWFc2bEtHL0p4RUtU |
|
1645 |
+d3RTc0RTeDhsMTUvaHRmbWpOQ2tuTzhLVEFoKzhRQm5FbjZ0a2x5Y3BmeEIrTUxLRjFCM1Q1bjcv |
|
1646 |
+T0VUMExMdmgxU2k1bnRRNXhTUHZZNWtXeUMyZjhXUXFZb3FSNU5JVENMeDV6dWNsQ3dGb2kvVXc4 |
|
1647 |
+OWNNWjM1MHBSbThzUktJbjJFeDUrQ1JwS3ZHdnBHbFJaTmk5VHZmVkNic1FCalR3MC9aeklTdzVQ |
|
1648 |
+NW9BVWE2U1ExUVFnNHg4VUNkY0s2QUNLaFluY0d4TVE9PVBLAwQKAAAAAABDj0lZGk9LVj8CAAA/ |
|
1649 |
+AgAAAgAAADE0eyJ2ZXJzaW9uIjoxfQpBY1g2NVpMUWk4ck9pUlIyWGEwQlFHQVhQVWF2aHNJVGVY |
|
1650 |
+c2dzRk9OUmFTRzJCQlg0SGxJRHpwRUd5aDUrZ2czZVRwWDFNOERua3pMeTVzcWRkMFpmK3padTgz |
|
1651 |
+Qm52Y1JPREVIVDllUW91YUtPTWltdlRYanNuSXAxUHo5VGY1TlRkRjNJVTd2V1lhUDg4WTI5NG1i |
|
1652 |
+c1VVL2RKVTZqZ3ZDbUw2cE1VZ28xUU12bGJnaVp3cDV1RDFQZXlrSXdKVWdJSEgxTEpnYi9xU2tW |
|
1653 |
+c25leW1XY1RXR0NobzRvZGx3S2hJWmFCelhvNFhlN2U1V2I2VHA3Rkk5VUpVcmZIRTAvcVdrZUZE |
|
1654 |
+VmxlazY3cUx3ZFZXcU9DdFk9CkFhSGR0QjhydmQ0U3N4ZmJ5eU1OOHIzZEoxeHA5NmFIRTQvalNi |
|
1655 |
+Z05hZWttaDkyb2ROM1F4MUlqYXZsYVkxeEt1eFF3KzlwTHFIcTF5a1JSRjQzL2RVWGFIRk5UU0NX |
|
1656 |
+OVFsdmd3KzMwa1ZhSEdXRllvbFRnRWE4djQ3b3VrbGlmc01PZGM0YVNKb2R4ZUFJcVc3Q1cwdDVR |
|
1657 |
+b2RUbWREUXpqc3phZkQ4R2VOd2NFQjdGMHI2RzNoZEJlQndxd3Z6eENVYnpSUmU5bEQ3NjQ3RFp1 |
|
1658 |
+bEo1U3c4amlvV0paTW40NlZhV3BYUXk4UnNva3hHaW00WUpybUZIQ2JkVU9qSWJsUmQ1Z3VhUDNU |
|
1659 |
+M0NxeHRPdC94b1BhOD1QSwMECgAAAAAAQ49JWVJM8QYXAgAAFwIAAAIAAAAxNnsidmVyc2lvbiI6 |
|
1660 |
+MX0KQVlCWDF6M21qUlQrand4M2FyNkFpemxnalJZbUM0ZHg5NkxVQVBTVHNMWXJKVHFtWnd5N0Jy |
|
1661 |
+OFlCcElVamorMHdlT3lNaUtLVnFwaER3RXExNWFqUmlSZUVEQURTVHZwWmlLZUlnZjR5elUzZXNP |
|
1662 |
+eDJ2U2J1bXhTK0swUGZVa2tsSy9TRmRiU3EvUHFMRjBDRTVCMXNyKzJLYTB2WlJmak94R3VFeFRD |
|
1663 |
+RXozN0ZlWDNNR3NCNkhZVHEzaUJWcUR6NVB6eHpCWWM5Kyt6RitLS1RnMVp2NGRtRmVQTC9JSEY5 |
|
1664 |
+WnV6TWlqRXdCRkE3WnJ0dkRqd3ZYcWtsMVpsR0c4eUV3PT0KQVhUWkRLVnNleldpR1RMUVZqa2hX |
|
1665 |
+bXBnK05MYlM0M2MxZEpvK2xGcC9yWUJYZkw3Wll5cGdjWE5IWXNzd01nc2VSSTAzNmt6bGZkdGNa |
|
1666 |
+bTdiUUN6M2JuQmZ6ZlorZFFuT2Y5STVSU2l0QzB2UmsydkQrOFdwbmRPSzNucGY5S0VpWklOSzVq |
|
1667 |
+TEZGTTJDTkNmQzBabXNRUlF3T0k2N3l5ZHhjVnFDMXBnWHV6QXRXamlsSUpnN0p6eUtsY3BJUGJu |
|
1668 |
+SUc0UzRSUlhIdW1wZnpoeWFZWkd6T0FDamRSYTZIMWJxYkJkZXFaSHMvQXJvM25mVjdlbjhxSUE5 |
|
1669 |
+aVUrbnNweXFnPT1QSwECHgMKAAAAAABDj0lZ1ZXH3+YAAADmAAAABQAAAAAAAAAAAAAApIEAAAAA |
|
1670 |
+LmtleXNQSwECHgMKAAAAAABDj0lZ77OVHxcCAAAXAgAAAgAAAAAAAAAAAAAApIEJAQAAMGNQSwEC |
|
1671 |
+HgMKAAAAAABDj0lZGk9LVj8CAAA/AgAAAgAAAAAAAAAAAAAApIFAAwAAMTRQSwECHgMKAAAAAABD |
|
1672 |
+j0lZUkzxBhcCAAAXAgAAAgAAAAAAAAAAAAAApIGfBQAAMTZQSwUGAAAAAAQABADDAAAA1gcAAAAA |
|
1673 |
+""" |
|
1674 |
+""" |
|
1675 |
+A sample corrupted storeroom archive, encrypted with |
|
1676 |
+[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1677 |
+and then encoded in base64. |
|
1678 |
+ |
|
1679 |
+The archive contains a directory `/services/array/` whose list of child |
|
1680 |
+items are not all valid item names. See |
|
1681 |
+[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3_JAVASCRIPT_SOURCE`][] for |
|
1682 |
+the exact script that created this archive. |
|
1683 |
+""" |
|
1684 |
+ |
|
1685 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4_JAVASCRIPT_SOURCE = """ |
|
1686 |
+// Executed in the top-level directory of the vault project code, in Node.js. |
|
1687 |
+const storeroom = require('storeroom') |
|
1688 |
+const Store = require('./lib/store.js') |
|
1689 |
+let store = new Store(storeroom.createFileAdapter('./broken-dir'), 'vault key') |
|
1690 |
+await store._storeroom.put('/dir/subdir/', []) |
|
1691 |
+await store._storeroom.put('/dir/', []) |
|
1692 |
+// The resulting "broken-dir" was then zipped manually. |
|
1693 |
+""" |
|
1694 |
+""" |
|
1695 |
+The JavaScript source for the script that generated the storeroom |
|
1696 |
+archive in [`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4`][]. |
|
1697 |
+""" |
|
1698 |
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4 = b""" |
|
1699 |
+UEsDBAoAAAAAAE+5SVloORS+5gAAAOYAAAAFAAAALmtleXN7InZlcnNpb24iOjF9CkV6dWRoNkRQ |
|
1700 |
+YTlNSWFabHZ5TytVYTFuamhjV2hIaTFBU0lKYW5zcXBxVlA0blN2V0twUzdZOUc2bjFSbi8vUnVM |
|
1701 |
+VitwcHp5SC9RQk83R0hFenNVMzdCUzFwUmVVeGhxUVlVTE56OXZvQ0crM1ZaL3VncU44dDJiU05m |
|
1702 |
+Nyt5K3hiNng2aVlFUmNZYTJ0UkhzZVdIc0laTE9ha2lDb0lRVGV3cndwYjVMM2pnd0E3SXBzaDkz |
|
1703 |
+QkxHSzM5dXNYNmo0R0I2WkRUeW5JcGk4V3JkbDhnWVZCN0tVPVBLAwQKAAAAAABPuUlZ663uUhcC |
|
1704 |
+AAAXAgAAAgAAADAzeyJ2ZXJzaW9uIjoxfQpBV2wzS2gzd21ZSFVZZU1RR3BLSVowdVd1VXFna09h |
|
1705 |
+YmRjNzNYYXVsZTNtVS9sN2Zvd1AyS21jbFp3ZDM5V3lYVzRTcEw4R0l4YStDZW51S3V0Wm5nb0FR |
|
1706 |
+bWlnaUJUbkFaais5TENCcGNIWlZNY2RBVkgxKzBFNGpsanZ1UkVwZ0tPS05LZjRsTUl1QnZ4VmFB |
|
1707 |
+ZkdwNHJYNEZ4MmpPSlk1Y3NQZzBBRFBoZVAwN29GWVQ3alorSUNEK1AxNGZPdWpwMGRUeDRrTDIy |
|
1708 |
+LzlqalRDNXBCNVF5NW5iOUx3Zk5DUWViSUVpaTZpbU0vRmFrK1dtV05tMndqMERSTEc4RHY3ZkE9 |
|
1709 |
+PQpBU0c3NTNGTVVwWmxjK3E1YXRzcC93OUNqN2JPOFlpY24wZHg2UGloTmwzUS9WSjVVeGJmU3l0 |
|
1710 |
+ZDFDNDBRU2xXeTJqOTJDWUd3VER6eEdBMXVnb0FCYi9kTllTelVwbHJFb3BuUVphYXdsdTVwV2x0 |
|
1711 |
+Y1E5WTcveWN4S2E4b0JaaGY3RkFYcGo2c01wUW9zNzI5VFVabFd4UmI4VFRtN2FrVnR1OXcvYXlK |
|
1712 |
+RS9reDh4ZUYxSGJlc3Q4N1IxTGg2ODd3dS9XVUN2ZjNXYXo1VjNnZWY0RnpUTXg0bkpqSlZOd0U0 |
|
1713 |
+SzAxUTlaVzQ0bmVvbExPUVI1MkZDeDZvbml3RW9tenc9PVBLAwQKAAAAAABPuUlZRXky4CsCAAAr |
|
1714 |
+AgAAAgAAADEweyJ2ZXJzaW9uIjoxfQpBWmlYWVlvNUdCY2d5dkFRaGtyK2ZjUkdVSkdabDd2dE5w |
|
1715 |
+T2Mrd1VzbXJhQWhRN3dKdlYraGhKcTlrcWNKQnBWU0gyUTBTTVVhb29iNjBJM1NYNUNtTkJRU2FH |
|
1716 |
+M3prd0Y0T2F4TnpCZUh0NFlpaDd4Y3p2ak4xR0hISDJQYW0xam05K09ja3JLVmNMVURtNXRKb2ZC |
|
1717 |
+Z1E4Q2NwMGZMVkdEaURjNWF0MjVMc2piQVcvNkZFSnJ5VVBHWis4UVdYRmlWMGdtVVZybVc3VUFy |
|
1718 |
+dGhJQitWNTdZS1BORi95Nng2OU43UTFQbmp1cUczdlpybzljMEJ3d012NWoyc3BMMTJHcTdzTDZE |
|
1719 |
+alB1d0dHbnB2MkVZQTFLbmc9CkFTdjQwUkgzRmxzbGVlU1NjRlZNRmh3dEx6eEYxK2xpcmxEL29X |
|
1720 |
+alJLQ05qVWZhUVpJTWpqMWRoVkhOakNUTWhWZ1ZONkl3b04xTnFOMEV6cmdhaTFBWnNiMm9UczYw |
|
1721 |
+QkI1UGh0U0hhQ2U2WllUeE1JemFPS2FIK0w2eHhtaXIrTlQxNTRXS0x5amJMams3MU1na3Nwa0Yy |
|
1722 |
+WDBJMnlaWW5IUUM0bmdEL24yZzRtSVI2Q1hWL0JOUXNzeTBEeXdGLzN6eGRRYWw5cFBtVk1qYnFu |
|
1723 |
+cHY5SFNqRTg4S25naVpBWFhJWU1OVGF2L3Q3Y3dEWGdNekhKTlU0Y2xnVUtIQVZ3QT09UEsDBAoA |
|
1724 |
+AAAAAE+5SVkPfKx9FwIAABcCAAACAAAAMWR7InZlcnNpb24iOjF9CkFYbHNLRzQwZG5ibTJvcXdY |
|
1725 |
+U2ZrSWp3Mmxpa0lDS3hVOXU3TU52VkZ1NEJ2R1FVVitSVVdsS3MxL25TSlBtM2U2OTRvVHdoeDFo |
|
1726 |
+RFF3U0M5U0QvbXd5bnpjSTloUnRCUWVXMkVMOVU5L1ZGcHFsVWY3Z1ZOMHZ0ZWpXYnV4QnhsZlRD |
|
1727 |
+Tys4SFBwU2Zaa2VOUld5R2JNdzBFSU9LTmxRYjk3OUF0c1g3THR0NytaTkJnakZHYkZxaHdwa3kx |
|
1728 |
+WUNDVng1UmNZZ2tma2ZjWnVncGpzc1RzNVFvK1p3QXBEcDZ4V3JjSHMxUDhvNktBRzAwcjZZbkNM |
|
1729 |
+N2ErU1dwZmVNTUJhZz09CkFadVF0cFZMWmVvb292NkdyQlpnb3B6VmRGUXBlK1h6QXZuZ2dPVnZM |
|
1730 |
+VWtCYVF2akl5K1VLdXVUVlFoQ1JiMVp6dGZQL2dsNnoxOEsyZW5sQlo2bGJTZnoxTlBWeUVzYXB3 |
|
1731 |
+dDVpUVh4azd5UkJlZks1cFlsNTduUXlmcFZQbzlreFpnOVdHTkV3NVJ5MkExemhnNGl6TWxLRmJh |
|
1732 |
+UjZFZ0FjQ3NFOXAveGRLa29ZNjhOUlZmNXJDM3lMQjc3ZWgyS1hCUld2WDNZcE9XdW00OGtsbmtI |
|
1733 |
+akJjMFpiQmUrT3NZb3d5cXpoRFA2ZGQxRlFnMlFjK09vc3B4V0sycld4M01HZz09UEsBAh4DCgAA |
|
1734 |
+AAAAT7lJWWg5FL7mAAAA5gAAAAUAAAAAAAAAAAAAAKSBAAAAAC5rZXlzUEsBAh4DCgAAAAAAT7lJ |
|
1735 |
+Weut7lIXAgAAFwIAAAIAAAAAAAAAAAAAAKSBCQEAADAzUEsBAh4DCgAAAAAAT7lJWUV5MuArAgAA |
|
1736 |
+KwIAAAIAAAAAAAAAAAAAAKSBQAMAADEwUEsBAh4DCgAAAAAAT7lJWQ98rH0XAgAAFwIAAAIAAAAA |
|
1737 |
+AAAAAAAAAKSBiwUAADFkUEsFBgAAAAAEAAQAwwAAAMIHAAAAAA== |
|
1738 |
+""" |
|
1739 |
+""" |
|
1740 |
+A sample corrupted storeroom archive, encrypted with |
|
1741 |
+[`VAULT_MASTER_KEY`][]. The configuration is compressed (zip archive) |
|
1742 |
+and then encoded in base64. |
|
1743 |
+ |
|
1744 |
+The archive contains two directories `/dir/` and `/dir/subdir/`, where |
|
1745 |
+`/dir/subdir/` is a correctly serialized directory, but `/dir/` does not |
|
1746 |
+contain `/dir/subdir/` in its list of child items. See |
|
1747 |
+[`VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4_JAVASCRIPT_SOURCE`][] for |
|
1748 |
+the exact script that created this archive. |
|
1749 |
+""" |
|
1750 |
+ |
|
1751 |
+# Error messages |
|
1752 |
+# ============== |
|
1753 |
+ |
|
1754 |
+CANNOT_LOAD_CRYPTOGRAPHY = ( |
|
1755 |
+ "Cannot load the required Python module 'cryptography'." |
|
1756 |
+) |
|
1757 |
+""" |
|
1758 |
+The expected `derivepassphrase` error message when the `cryptography` |
|
1759 |
+module cannot be loaded, which is needed e.g. by the `export vault` |
|
1760 |
+subcommands. |
|
1761 |
+""" |
... | ... |
@@ -0,0 +1,272 @@ |
1 |
+# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: Zlib |
|
4 |
+ |
|
5 |
+from __future__ import annotations |
|
6 |
+ |
|
7 |
+import os |
|
8 |
+import shlex |
|
9 |
+import stat |
|
10 |
+from typing import TYPE_CHECKING |
|
11 |
+ |
|
12 |
+import tests.data |
|
13 |
+from derivepassphrase import _types, ssh_agent, vault |
|
14 |
+ |
|
15 |
+__all__ = () |
|
16 |
+ |
|
17 |
+if TYPE_CHECKING: |
|
18 |
+ import socket |
|
19 |
+ from collections.abc import Iterator |
|
20 |
+ |
|
21 |
+ from typing_extensions import Any |
|
22 |
+ |
|
23 |
+ |
|
24 |
+# Stubbed actions |
|
25 |
+# =============== |
|
26 |
+ |
|
27 |
+ |
|
28 |
+# SSH agent client |
|
29 |
+# ---------------- |
|
30 |
+ |
|
31 |
+ |
|
32 |
+def list_keys(self: Any = None) -> list[_types.SSHKeyCommentPair]: |
|
33 |
+ """Return a list of all SSH test keys, as key/comment pairs. |
|
34 |
+ |
|
35 |
+ Intended as a monkeypatching replacement for |
|
36 |
+ [`ssh_agent.SSHAgentClient.list_keys`][]. |
|
37 |
+ |
|
38 |
+ """ |
|
39 |
+ del self # Unused. |
|
40 |
+ Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
41 |
+ return [ |
|
42 |
+ Pair(value.public_key_data, f"{key} test key".encode("ASCII")) |
|
43 |
+ for key, value in tests.data.ALL_KEYS.items() |
|
44 |
+ ] |
|
45 |
+ |
|
46 |
+ |
|
47 |
+def sign( |
|
48 |
+ self: Any, key: bytes | bytearray, message: bytes | bytearray |
|
49 |
+) -> bytes: |
|
50 |
+ """Return the signature of `message` under `key`. |
|
51 |
+ |
|
52 |
+ Can only handle keys in [`SUPPORTED_KEYS`][], and only the vault |
|
53 |
+ UUID as the message. |
|
54 |
+ |
|
55 |
+ Intended as a monkeypatching replacement for |
|
56 |
+ [`ssh_agent.SSHAgentClient.sign`][]. |
|
57 |
+ |
|
58 |
+ """ |
|
59 |
+ del self # Unused. |
|
60 |
+ assert message == vault.Vault.UUID |
|
61 |
+ for value in tests.data.SUPPORTED_KEYS.values(): |
|
62 |
+ if value.public_key_data == key: # pragma: no branch |
|
63 |
+ return value.expected_signatures[ |
|
64 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
65 |
+ ].signature |
|
66 |
+ raise AssertionError |
|
67 |
+ |
|
68 |
+ |
|
69 |
+def list_keys_singleton(self: Any = None) -> list[_types.SSHKeyCommentPair]: |
|
70 |
+ """Return a singleton list of the first supported SSH test key. |
|
71 |
+ |
|
72 |
+ The key is returned as a key/comment pair. |
|
73 |
+ |
|
74 |
+ Intended as a monkeypatching replacement for |
|
75 |
+ [`ssh_agent.SSHAgentClient.list_keys`][]. |
|
76 |
+ |
|
77 |
+ """ |
|
78 |
+ del self # Unused. |
|
79 |
+ Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
80 |
+ list1 = [ |
|
81 |
+ Pair(value.public_key_data, f"{key} test key".encode("ASCII")) |
|
82 |
+ for key, value in tests.data.SUPPORTED_KEYS.items() |
|
83 |
+ ] |
|
84 |
+ return list1[:1] |
|
85 |
+ |
|
86 |
+ |
|
87 |
+# CLI machinery |
|
88 |
+# ------------- |
|
89 |
+ |
|
90 |
+ |
|
91 |
+def suitable_ssh_keys(conn: Any) -> Iterator[_types.SSHKeyCommentPair]: |
|
92 |
+ """Return a two-item list of SSH test keys (key/comment pairs). |
|
93 |
+ |
|
94 |
+ Intended as a monkeypatching replacement for |
|
95 |
+ `cli_machinery.get_suitable_ssh_keys` to better script and test the |
|
96 |
+ interactive key selection. When used this way, `derivepassphrase` |
|
97 |
+ believes that only those two keys are loaded and suitable. |
|
98 |
+ |
|
99 |
+ """ |
|
100 |
+ del conn # Unused. |
|
101 |
+ Pair = _types.SSHKeyCommentPair # noqa: N806 |
|
102 |
+ yield from [ |
|
103 |
+ Pair(tests.data.DUMMY_KEY1, b"no comment"), |
|
104 |
+ Pair(tests.data.DUMMY_KEY2, b"a comment"), |
|
105 |
+ ] |
|
106 |
+ |
|
107 |
+ |
|
108 |
+def auto_prompt(*args: Any, **kwargs: Any) -> str: |
|
109 |
+ """Return [`DUMMY_PASSPHRASE`][]. |
|
110 |
+ |
|
111 |
+ Intended as a monkeypatching replacement for |
|
112 |
+ `cli.prompt_for_passphrase` to better script and test the |
|
113 |
+ interactive passphrase queries. |
|
114 |
+ |
|
115 |
+ """ |
|
116 |
+ del args, kwargs # Unused. |
|
117 |
+ return tests.data.DUMMY_PASSPHRASE |
|
118 |
+ |
|
119 |
+ |
|
120 |
+# `vault` module |
|
121 |
+# -------------- |
|
122 |
+ |
|
123 |
+ |
|
124 |
+def phrase_from_key( |
|
125 |
+ key: bytes, |
|
126 |
+ /, |
|
127 |
+ *, |
|
128 |
+ conn: ssh_agent.SSHAgentClient | socket.socket | None = None, |
|
129 |
+) -> bytes: |
|
130 |
+ """Return the "equivalent master passphrase" for key. |
|
131 |
+ |
|
132 |
+ Only works for key [`DUMMY_KEY1`][]. |
|
133 |
+ |
|
134 |
+ Intended as a monkeypatching replacement for |
|
135 |
+ [`vault.Vault.phrase_from_key`][], bypassing communication with an |
|
136 |
+ actual SSH agent. |
|
137 |
+ |
|
138 |
+ """ |
|
139 |
+ del conn |
|
140 |
+ if key == tests.data.DUMMY_KEY1: # pragma: no branch |
|
141 |
+ return tests.data.DUMMY_PHRASE_FROM_KEY1 |
|
142 |
+ raise KeyError(key) # pragma: no cover |
|
143 |
+ |
|
144 |
+ |
|
145 |
+# SSH agent socket provider data (with callables) |
|
146 |
+# =============================================== |
|
147 |
+ |
|
148 |
+ |
|
149 |
+def provider_entry_provider() -> _types.SSHAgentSocket: # pragma: no cover |
|
150 |
+ """A pseudo provider for a [`_types.SSHAgentSocketProviderEntry`][].""" |
|
151 |
+ msg = "We are not supposed to be called!" |
|
152 |
+ raise AssertionError(msg) |
|
153 |
+ |
|
154 |
+ |
|
155 |
+provider_entry1 = _types.SSHAgentSocketProviderEntry( |
|
156 |
+ provider_entry_provider, "entry1", ("entry1a", "entry1b", "entry1c") |
|
157 |
+) |
|
158 |
+"""A sample [`_types.SSHAgentSocketProviderEntry`][].""" |
|
159 |
+ |
|
160 |
+provider_entry2 = _types.SSHAgentSocketProviderEntry( |
|
161 |
+ provider_entry_provider, "entry2", ("entry2d", "entry2e") |
|
162 |
+) |
|
163 |
+ |
|
164 |
+ |
|
165 |
+# SSH agent output parsing |
|
166 |
+# ======================== |
|
167 |
+ |
|
168 |
+ |
|
169 |
+def parse_sh_export_line(line: str, *, env_name: str) -> str: |
|
170 |
+ """Parse the output of typical SSH agents' SSH_AUTH_SOCK lines. |
|
171 |
+ |
|
172 |
+ Intentionally parses only a small subset of sh(1) syntax which works |
|
173 |
+ with current OpenSSH and PuTTY output. We require exactly one |
|
174 |
+ variable setting, and one export instruction, both on the same line, |
|
175 |
+ and perhaps combined into one statement. Terminating semicolons |
|
176 |
+ after each command are ignored. |
|
177 |
+ |
|
178 |
+ Args: |
|
179 |
+ line: |
|
180 |
+ A line of sh(1) script to parse. |
|
181 |
+ env_name: |
|
182 |
+ The name of the environment variable to expect. |
|
183 |
+ |
|
184 |
+ Returns: |
|
185 |
+ The parsed environment variable value. |
|
186 |
+ |
|
187 |
+ Raises: |
|
188 |
+ ValueError: |
|
189 |
+ Cannot parse the sh script. Perhaps it is too complex, |
|
190 |
+ perhaps it is malformed. |
|
191 |
+ |
|
192 |
+ """ |
|
193 |
+ line = line.rstrip("\r\n") |
|
194 |
+ shlex_parser = shlex.shlex( |
|
195 |
+ instream=line, posix=True, punctuation_chars=True |
|
196 |
+ ) |
|
197 |
+ shlex_parser.whitespace = " \t" |
|
198 |
+ tokens = list(shlex_parser) |
|
199 |
+ orig_tokens = tokens.copy() |
|
200 |
+ if tokens[-1] == ";": |
|
201 |
+ tokens.pop() |
|
202 |
+ if tokens[-3:] == [";", "export", env_name]: |
|
203 |
+ tokens[-3:] = [] |
|
204 |
+ tokens[:0] = ["export"] |
|
205 |
+ if not ( |
|
206 |
+ len(tokens) == 2 |
|
207 |
+ and tokens[0] == "export" |
|
208 |
+ and tokens[1].startswith(f"{env_name}=") |
|
209 |
+ ): |
|
210 |
+ msg = f"Cannot parse sh line: {orig_tokens!r} -> {tokens!r}" |
|
211 |
+ raise ValueError(msg) |
|
212 |
+ return tokens[1].split("=", 1)[1] |
|
213 |
+ |
|
214 |
+ |
|
215 |
+# General file system actions |
|
216 |
+# =========================== |
|
217 |
+ |
|
218 |
+ |
|
219 |
+def make_file_readonly( |
|
220 |
+ pathname: str | bytes | os.PathLike[str], |
|
221 |
+ /, |
|
222 |
+ *, |
|
223 |
+ try_race_free_implementation: bool = True, |
|
224 |
+) -> None: |
|
225 |
+ """Mark a file as read-only. |
|
226 |
+ |
|
227 |
+ On POSIX, this entails removing the write permission bits for user, |
|
228 |
+ group and other, and ensuring the read permission bit for user is |
|
229 |
+ set. |
|
230 |
+ |
|
231 |
+ Unfortunately, The Annoying OS (a.k.a. Microsoft Windows) has its |
|
232 |
+ own rules: Set exactly(?) the read permission bit for user to make |
|
233 |
+ the file read-only, and set exactly(?) the write permission bit for |
|
234 |
+ user to make the file read/write; all other permission bit settings |
|
235 |
+ are ignored. |
|
236 |
+ |
|
237 |
+ The cross-platform procedure therefore is: |
|
238 |
+ |
|
239 |
+ 1. Call `os.stat` on the file, noting the permission bits. |
|
240 |
+ 2. Calculate the new permission bits POSIX-style. |
|
241 |
+ 3. Call `os.chmod` with permission bit `stat.S_IREAD`. |
|
242 |
+ 4. Call `os.chmod` with the correct POSIX-style permissions. |
|
243 |
+ |
|
244 |
+ If the platform supports it, we use a file descriptor instead of |
|
245 |
+ a path name. Otherwise, we use the same path name multiple times, |
|
246 |
+ and are susceptible to race conditions. |
|
247 |
+ |
|
248 |
+ """ |
|
249 |
+ fname: int | str | bytes | os.PathLike |
|
250 |
+ if try_race_free_implementation and {os.stat, os.chmod} <= os.supports_fd: |
|
251 |
+ # The Annoying OS (v11 at least) supports fstat and fchmod, but |
|
252 |
+ # does not support changing the file mode on file descriptors |
|
253 |
+ # for read-only files. |
|
254 |
+ fname = os.open( |
|
255 |
+ pathname, |
|
256 |
+ os.O_RDWR |
|
257 |
+ | getattr(os, "O_CLOEXEC", 0) |
|
258 |
+ | getattr(os, "O_NOCTTY", 0), |
|
259 |
+ ) |
|
260 |
+ else: |
|
261 |
+ fname = pathname |
|
262 |
+ try: |
|
263 |
+ orig_mode = os.stat(fname).st_mode # noqa: PTH116 |
|
264 |
+ new_mode = ( |
|
265 |
+ orig_mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH |
|
266 |
+ | stat.S_IREAD |
|
267 |
+ ) |
|
268 |
+ os.chmod(fname, stat.S_IREAD) # noqa: PTH101 |
|
269 |
+ os.chmod(fname, new_mode) # noqa: PTH101 |
|
270 |
+ finally: |
|
271 |
+ if isinstance(fname, int): |
|
272 |
+ os.close(fname) |
... | ... |
@@ -0,0 +1,644 @@ |
1 |
+# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: Zlib |
|
4 |
+ |
|
5 |
+from __future__ import annotations |
|
6 |
+ |
|
7 |
+import contextlib |
|
8 |
+import errno |
|
9 |
+import logging |
|
10 |
+import os |
|
11 |
+import re |
|
12 |
+import sys |
|
13 |
+from typing import TYPE_CHECKING, TypedDict |
|
14 |
+ |
|
15 |
+import click.testing |
|
16 |
+from typing_extensions import NamedTuple |
|
17 |
+ |
|
18 |
+import tests.data |
|
19 |
+from derivepassphrase import _types, cli, ssh_agent, vault |
|
20 |
+from derivepassphrase.ssh_agent import socketprovider |
|
21 |
+ |
|
22 |
+__all__ = () |
|
23 |
+ |
|
24 |
+if TYPE_CHECKING: |
|
25 |
+ from collections.abc import Callable, Iterator, Mapping, Sequence |
|
26 |
+ from contextlib import AbstractContextManager |
|
27 |
+ from typing import IO, NotRequired |
|
28 |
+ |
|
29 |
+ from typing_extensions import Any, Buffer, Self |
|
30 |
+ |
|
31 |
+ |
|
32 |
+# Test suite settings |
|
33 |
+# =================== |
|
34 |
+ |
|
35 |
+MIN_CONCURRENCY = 4 |
|
36 |
+""" |
|
37 |
+The minimum amount of concurrent threads used for testing. |
|
38 |
+""" |
|
39 |
+ |
|
40 |
+ |
|
41 |
+def get_concurrency_limit() -> int: |
|
42 |
+ """Return the imposed limit on the number of concurrent threads. |
|
43 |
+ |
|
44 |
+ We use [`os.process_cpu_count`][] as the limit on Python 3.13 and |
|
45 |
+ higher, and [`os.cpu_count`][] on Python 3.12 and below. On |
|
46 |
+ Python 3.12 and below, we explicitly support the `PYTHON_CPU_COUNT` |
|
47 |
+ environment variable. We guarantee at least [`MIN_CONCURRENCY`][] |
|
48 |
+ many threads in any case. |
|
49 |
+ |
|
50 |
+ """ # noqa: RUF002 |
|
51 |
+ result: int | None = None |
|
52 |
+ if sys.version_info >= (3, 13): |
|
53 |
+ result = os.process_cpu_count() |
|
54 |
+ else: |
|
55 |
+ with contextlib.suppress(KeyError, ValueError): |
|
56 |
+ result = result or int(os.environ["PYTHON_CPU_COUNT"], 10) |
|
57 |
+ with contextlib.suppress(AttributeError): |
|
58 |
+ result = result or len(os.sched_getaffinity(os.getpid())) |
|
59 |
+ return max(result if result is not None else 0, MIN_CONCURRENCY) |
|
60 |
+ |
|
61 |
+ |
|
62 |
+# Log/Error message searching |
|
63 |
+# =========================== |
|
64 |
+ |
|
65 |
+ |
|
66 |
+def message_emitted_factory( |
|
67 |
+ level: int, |
|
68 |
+ *, |
|
69 |
+ logger_name: str = cli.PROG_NAME, |
|
70 |
+) -> Callable[[str | re.Pattern[str], Sequence[tuple[str, int, str]]], bool]: |
|
71 |
+ """Return a function to test if a matching message was emitted. |
|
72 |
+ |
|
73 |
+ Args: |
|
74 |
+ level: The level to match messages at. |
|
75 |
+ logger_name: The name of the logger to match against. |
|
76 |
+ |
|
77 |
+ """ |
|
78 |
+ |
|
79 |
+ def message_emitted( |
|
80 |
+ text: str | re.Pattern[str], |
|
81 |
+ record_tuples: Sequence[tuple[str, int, str]], |
|
82 |
+ ) -> bool: |
|
83 |
+ """Return true if a matching message was emitted. |
|
84 |
+ |
|
85 |
+ Args: |
|
86 |
+ text: Substring or pattern to match against. |
|
87 |
+ record_tuples: Items to match. |
|
88 |
+ |
|
89 |
+ """ |
|
90 |
+ |
|
91 |
+ def check_record(record: tuple[str, int, str]) -> bool: |
|
92 |
+ if record[:2] != (logger_name, level): |
|
93 |
+ return False |
|
94 |
+ if isinstance(text, str): |
|
95 |
+ return text in record[2] |
|
96 |
+ return text.match(record[2]) is not None # pragma: no cover |
|
97 |
+ |
|
98 |
+ return any(map(check_record, record_tuples)) |
|
99 |
+ |
|
100 |
+ return message_emitted |
|
101 |
+ |
|
102 |
+ |
|
103 |
+# No need to assert debug messages as of yet. |
|
104 |
+info_emitted = message_emitted_factory(logging.INFO) |
|
105 |
+warning_emitted = message_emitted_factory(logging.WARNING) |
|
106 |
+deprecation_warning_emitted = message_emitted_factory( |
|
107 |
+ logging.WARNING, logger_name=f"{cli.PROG_NAME}.deprecation" |
|
108 |
+) |
|
109 |
+deprecation_info_emitted = message_emitted_factory( |
|
110 |
+ logging.INFO, logger_name=f"{cli.PROG_NAME}.deprecation" |
|
111 |
+) |
|
112 |
+error_emitted = message_emitted_factory(logging.ERROR) |
|
113 |
+ |
|
114 |
+ |
|
115 |
+# click.testing.CliRunner handling |
|
116 |
+# ================================ |
|
117 |
+ |
|
118 |
+ |
|
119 |
+class ReadableResult(NamedTuple): |
|
120 |
+ """Helper class for formatting and testing click.testing.Result objects.""" |
|
121 |
+ |
|
122 |
+ exception: BaseException | None |
|
123 |
+ exit_code: int |
|
124 |
+ stdout: str |
|
125 |
+ stderr: str |
|
126 |
+ |
|
127 |
+ def clean_exit( |
|
128 |
+ self, *, output: str = "", empty_stderr: bool = False |
|
129 |
+ ) -> bool: |
|
130 |
+ """Return whether the invocation exited cleanly. |
|
131 |
+ |
|
132 |
+ Args: |
|
133 |
+ output: |
|
134 |
+ An expected output string. |
|
135 |
+ |
|
136 |
+ """ |
|
137 |
+ return ( |
|
138 |
+ ( |
|
139 |
+ not self.exception |
|
140 |
+ or ( |
|
141 |
+ isinstance(self.exception, SystemExit) |
|
142 |
+ and self.exit_code == 0 |
|
143 |
+ ) |
|
144 |
+ ) |
|
145 |
+ and (not output or output in self.stdout) |
|
146 |
+ and (not empty_stderr or not self.stderr) |
|
147 |
+ ) |
|
148 |
+ |
|
149 |
+ def error_exit( |
|
150 |
+ self, |
|
151 |
+ *, |
|
152 |
+ error: str | re.Pattern[str] | type[BaseException] = BaseException, |
|
153 |
+ record_tuples: Sequence[tuple[str, int, str]] = (), |
|
154 |
+ ) -> bool: |
|
155 |
+ """Return whether the invocation exited uncleanly. |
|
156 |
+ |
|
157 |
+ Args: |
|
158 |
+ error: |
|
159 |
+ An expected error message, or an expected numeric error |
|
160 |
+ code, or an expected exception type. |
|
161 |
+ |
|
162 |
+ """ |
|
163 |
+ |
|
164 |
+ def error_match(error: str | re.Pattern[str], line: str) -> bool: |
|
165 |
+ return ( |
|
166 |
+ error in line |
|
167 |
+ if isinstance(error, str) |
|
168 |
+ else error.match(line) is not None |
|
169 |
+ ) |
|
170 |
+ |
|
171 |
+ # TODO(the-13th-letter): Rewrite using structural pattern matching. |
|
172 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
173 |
+ if isinstance(error, type): |
|
174 |
+ return isinstance(self.exception, error) |
|
175 |
+ else: # noqa: RET505 |
|
176 |
+ assert isinstance(error, (str, re.Pattern)) |
|
177 |
+ return ( |
|
178 |
+ isinstance(self.exception, SystemExit) |
|
179 |
+ and self.exit_code > 0 |
|
180 |
+ and ( |
|
181 |
+ not error |
|
182 |
+ or any( |
|
183 |
+ error_match(error, line) |
|
184 |
+ for line in self.stderr.splitlines(True) |
|
185 |
+ ) |
|
186 |
+ or tests.machinery.error_emitted(error, record_tuples) |
|
187 |
+ ) |
|
188 |
+ ) |
|
189 |
+ |
|
190 |
+ |
|
191 |
+class CliRunner: |
|
192 |
+ """An abstracted CLI runner class. |
|
193 |
+ |
|
194 |
+ Intended to provide similar functionality and scope as the |
|
195 |
+ [`click.testing.CliRunner`][] class, though not necessarily |
|
196 |
+ `click`-specific. Also allows for seamless migration away from |
|
197 |
+ `click`, if/when we decide this. |
|
198 |
+ |
|
199 |
+ """ |
|
200 |
+ |
|
201 |
+ _SUPPORTS_MIX_STDERR_ATTRIBUTE = not hasattr(click.testing, "StreamMixer") |
|
202 |
+ """ |
|
203 |
+ True if and only if [`click.testing.CliRunner`][] supports the |
|
204 |
+ `mix_stderr` attribute. It was removed in 8.2.0 in favor of the |
|
205 |
+ `click.testing.StreamMixer` class. |
|
206 |
+ |
|
207 |
+ See also |
|
208 |
+ [`pallets/click#2523`](https://github.com/pallets/click/pull/2523). |
|
209 |
+ """ |
|
210 |
+ |
|
211 |
+ def __init__( |
|
212 |
+ self, |
|
213 |
+ *, |
|
214 |
+ mix_stderr: bool = False, |
|
215 |
+ color: bool | None = None, |
|
216 |
+ ) -> None: |
|
217 |
+ self.color = color |
|
218 |
+ self.mix_stderr = mix_stderr |
|
219 |
+ |
|
220 |
+ class MixStderrAttribute(TypedDict): |
|
221 |
+ mix_stderr: NotRequired[bool] |
|
222 |
+ |
|
223 |
+ mix_stderr_args: MixStderrAttribute = ( |
|
224 |
+ {"mix_stderr": mix_stderr} |
|
225 |
+ if self._SUPPORTS_MIX_STDERR_ATTRIBUTE |
|
226 |
+ else {} |
|
227 |
+ ) |
|
228 |
+ self.click_testing_clirunner = click.testing.CliRunner( |
|
229 |
+ **mix_stderr_args |
|
230 |
+ ) |
|
231 |
+ |
|
232 |
+ def invoke( |
|
233 |
+ self, |
|
234 |
+ cli: click.BaseCommand, |
|
235 |
+ args: Sequence[str] | str | None = None, |
|
236 |
+ input: str | bytes | IO[Any] | None = None, |
|
237 |
+ env: Mapping[str, str | None] | None = None, |
|
238 |
+ catch_exceptions: bool = True, |
|
239 |
+ color: bool | None = None, |
|
240 |
+ **extra: Any, |
|
241 |
+ ) -> ReadableResult: |
|
242 |
+ if color is None: # pragma: no cover |
|
243 |
+ color = self.color if self.color is not None else False |
|
244 |
+ raw_result = self.click_testing_clirunner.invoke( |
|
245 |
+ cli, |
|
246 |
+ args=args, |
|
247 |
+ input=input, |
|
248 |
+ env=env, |
|
249 |
+ catch_exceptions=catch_exceptions, |
|
250 |
+ color=color, |
|
251 |
+ **extra, |
|
252 |
+ ) |
|
253 |
+ # In 8.2.0, r.stdout is no longer a property aliasing the |
|
254 |
+ # `output` attribute, but rather the raw stdout value. |
|
255 |
+ try: |
|
256 |
+ stderr = raw_result.stderr |
|
257 |
+ except ValueError: |
|
258 |
+ stderr = raw_result.stdout |
|
259 |
+ return ReadableResult( |
|
260 |
+ raw_result.exception, |
|
261 |
+ raw_result.exit_code, |
|
262 |
+ (raw_result.stdout if not self.mix_stderr else raw_result.output) |
|
263 |
+ or "", |
|
264 |
+ stderr or "", |
|
265 |
+ ) |
|
266 |
+ return ReadableResult.parse(raw_result) |
|
267 |
+ |
|
268 |
+ def isolated_filesystem( |
|
269 |
+ self, |
|
270 |
+ temp_dir: str | os.PathLike[str] | None = None, |
|
271 |
+ ) -> AbstractContextManager[str]: |
|
272 |
+ return self.click_testing_clirunner.isolated_filesystem( |
|
273 |
+ temp_dir=temp_dir |
|
274 |
+ ) |
|
275 |
+ |
|
276 |
+ |
|
277 |
+# Stubbed SSH agent socket |
|
278 |
+# ======================== |
|
279 |
+ |
|
280 |
+# Base variant |
|
281 |
+# ------------ |
|
282 |
+ |
|
283 |
+ |
|
284 |
+@socketprovider.SocketProvider.register("stub_agent") |
|
285 |
+class StubbedSSHAgentSocket: |
|
286 |
+ """A stubbed SSH agent presenting an [`_types.SSHAgentSocket`][].""" |
|
287 |
+ |
|
288 |
+ _SOCKET_IS_CLOSED = "Socket is closed." |
|
289 |
+ _NO_FLAG_SUPPORT = "This stubbed SSH agent socket does not support flags." |
|
290 |
+ _PROTOCOL_VIOLATION = "SSH agent protocol violation." |
|
291 |
+ _INVALID_REQUEST = "Invalid request." |
|
292 |
+ _UNSUPPORTED_REQUEST = "Unsupported request." |
|
293 |
+ |
|
294 |
+ HEADER_SIZE = 4 |
|
295 |
+ CODE_SIZE = 1 |
|
296 |
+ |
|
297 |
+ KNOWN_EXTENSIONS = frozenset({ |
|
298 |
+ "query", |
|
299 |
+ "list-extended@putty.projects.tartarus.org", |
|
300 |
+ }) |
|
301 |
+ """Known and implemented protocol extensions.""" |
|
302 |
+ |
|
303 |
+ def __init__(self, *extensions: str) -> None: |
|
304 |
+ """Initialize the agent.""" |
|
305 |
+ self.send_to_client = bytearray() |
|
306 |
+ """ |
|
307 |
+ The buffered response to the client, read piecemeal by [`recv`][]. |
|
308 |
+ """ |
|
309 |
+ self.receive_from_client = bytearray() |
|
310 |
+ """The last request issued by the client.""" |
|
311 |
+ self.closed = False |
|
312 |
+ """True if the connection is closed, false otherwise.""" |
|
313 |
+ self.enabled_extensions = frozenset(extensions) & self.KNOWN_EXTENSIONS |
|
314 |
+ """ |
|
315 |
+ Extensions actually enabled in this particular stubbed SSH agent. |
|
316 |
+ """ |
|
317 |
+ self.try_rfc6979 = False |
|
318 |
+ """ |
|
319 |
+ Attempt to issue DSA and ECDSA signatures according to RFC 6979? |
|
320 |
+ """ |
|
321 |
+ self.try_pageant_068_080 = False |
|
322 |
+ """ |
|
323 |
+ Attempt to issue DSA and ECDSA signatures as per Pageant 0.68–0.80? |
|
324 |
+ """ # noqa: RUF001 |
|
325 |
+ |
|
326 |
+ def __enter__(self) -> Self: |
|
327 |
+ """Return self.""" |
|
328 |
+ return self |
|
329 |
+ |
|
330 |
+ def __exit__(self, *args: object) -> None: |
|
331 |
+ """Mark the agent's socket as closed.""" |
|
332 |
+ self.closed = True |
|
333 |
+ |
|
334 |
+ def sendall(self, data: Buffer, flags: int = 0, /) -> None: |
|
335 |
+ """Send data to the SSH agent. |
|
336 |
+ |
|
337 |
+ The signature, and behavior, is identical to |
|
338 |
+ [`socket.socket.sendall`][]. Upon successful sending, this |
|
339 |
+ agent will parse the request, call the appropriate handler, and |
|
340 |
+ buffer the result such that it can be read via [`recv`][], in |
|
341 |
+ accordance with the SSH agent protocol. |
|
342 |
+ |
|
343 |
+ Args: |
|
344 |
+ data: Binary data to send to the agent. |
|
345 |
+ flags: Reserved. Must be 0. |
|
346 |
+ |
|
347 |
+ Returns: |
|
348 |
+ Nothing. The result should be requested via [`recv`][], and |
|
349 |
+ interpreted in accordance with the SSH agent protocol. |
|
350 |
+ |
|
351 |
+ Raises: |
|
352 |
+ AssertionError: |
|
353 |
+ The flags argument, if specified, must be 0. |
|
354 |
+ ValueError: |
|
355 |
+ The agent's socket is already closed. No further |
|
356 |
+ requests can be sent. |
|
357 |
+ |
|
358 |
+ """ |
|
359 |
+ assert not flags, self._NO_FLAG_SUPPORT |
|
360 |
+ if self.closed: |
|
361 |
+ raise ValueError(self._SOCKET_IS_CLOSED) |
|
362 |
+ self.receive_from_client.extend(memoryview(data)) |
|
363 |
+ try: |
|
364 |
+ self.parse_client_request_and_dispatch() |
|
365 |
+ except ValueError: |
|
366 |
+ payload = int.to_bytes(_types.SSH_AGENT.FAILURE.value, 1, "big") |
|
367 |
+ self.send_to_client.extend(int.to_bytes(len(payload), 4, "big")) |
|
368 |
+ self.send_to_client.extend(payload) |
|
369 |
+ finally: |
|
370 |
+ self.receive_from_client.clear() |
|
371 |
+ |
|
372 |
+ def recv(self, count: int, flags: int = 0, /) -> bytes: |
|
373 |
+ """Read data from the SSH agent. |
|
374 |
+ |
|
375 |
+ As per the SSH agent protocol, data is only available to be read |
|
376 |
+ immediately after a request via [`sendall`][]. Calls to |
|
377 |
+ [`recv`][] at other points in time that attempt to read data |
|
378 |
+ violate the protocol, and will fail. Notwithstanding the last |
|
379 |
+ sentence, at any point in time, though pointless, it is |
|
380 |
+ additionally permissible to read 0 bytes from the agent, or any |
|
381 |
+ number of bytes from a closed socket. |
|
382 |
+ |
|
383 |
+ Args: |
|
384 |
+ count: |
|
385 |
+ Number of bytes to read from the agent. |
|
386 |
+ flags: |
|
387 |
+ Reserved. Must be 0. |
|
388 |
+ |
|
389 |
+ Returns: |
|
390 |
+ (A chunk of) the SSH agent's response to the most recent |
|
391 |
+ request. If reading 0 bytes, or if reading from a closed |
|
392 |
+ socket, the returned chunk is always an empty byte string. |
|
393 |
+ |
|
394 |
+ Raises: |
|
395 |
+ AssertionError: |
|
396 |
+ The flags argument, if specified, must be 0. |
|
397 |
+ |
|
398 |
+ Alternatively, `recv` was called when there was no |
|
399 |
+ response to be obtained, in violation of the SSH agent |
|
400 |
+ protocol. |
|
401 |
+ |
|
402 |
+ """ |
|
403 |
+ assert not flags, self._NO_FLAG_SUPPORT |
|
404 |
+ assert not count or self.closed or self.send_to_client, ( |
|
405 |
+ self._PROTOCOL_VIOLATION |
|
406 |
+ ) |
|
407 |
+ ret = bytes(self.send_to_client[:count]) |
|
408 |
+ del self.send_to_client[:count] |
|
409 |
+ return ret |
|
410 |
+ |
|
411 |
+ def parse_client_request_and_dispatch(self) -> None: |
|
412 |
+ """Parse the client request and call the matching handler. |
|
413 |
+ |
|
414 |
+ This agent supports the |
|
415 |
+ [`SSH_AGENTC_REQUEST_IDENTITIES`][_types.SSH_AGENTC.REQUEST_IDENTITIES], |
|
416 |
+ [`SSH_AGENTC_SIGN_REQUEST`][_types.SSH_AGENTC.SIGN_REQUEST] and |
|
417 |
+ the [`SSH_AGENTC_EXTENSION`][_types.SSH_AGENTC.EXTENSION] |
|
418 |
+ request types. |
|
419 |
+ |
|
420 |
+ """ |
|
421 |
+ |
|
422 |
+ if len(self.receive_from_client) < self.HEADER_SIZE + self.CODE_SIZE: |
|
423 |
+ raise ValueError(self._INVALID_REQUEST) |
|
424 |
+ target_header = ssh_agent.SSHAgentClient.uint32( |
|
425 |
+ len(self.receive_from_client) - self.HEADER_SIZE |
|
426 |
+ ) |
|
427 |
+ if target_header != self.receive_from_client[: self.HEADER_SIZE]: |
|
428 |
+ raise ValueError(self._INVALID_REQUEST) |
|
429 |
+ code = _types.SSH_AGENTC( |
|
430 |
+ int.from_bytes( |
|
431 |
+ self.receive_from_client[ |
|
432 |
+ self.HEADER_SIZE : self.HEADER_SIZE + self.CODE_SIZE |
|
433 |
+ ], |
|
434 |
+ "big", |
|
435 |
+ ) |
|
436 |
+ ) |
|
437 |
+ |
|
438 |
+ def is_enabled_extension(extension: str) -> bool: |
|
439 |
+ if ( |
|
440 |
+ extension not in self.enabled_extensions |
|
441 |
+ or code != _types.SSH_AGENTC.EXTENSION |
|
442 |
+ ): |
|
443 |
+ return False |
|
444 |
+ string = ssh_agent.SSHAgentClient.string |
|
445 |
+ extension_marker = b"\x1b" + string(extension.encode("ascii")) |
|
446 |
+ return self.receive_from_client.startswith(extension_marker, 4) |
|
447 |
+ |
|
448 |
+ result: Buffer | Iterator[int] |
|
449 |
+ if code == _types.SSH_AGENTC.REQUEST_IDENTITIES: |
|
450 |
+ result = self.request_identities(list_extended=False) |
|
451 |
+ elif code == _types.SSH_AGENTC.SIGN_REQUEST: |
|
452 |
+ result = self.sign() |
|
453 |
+ elif is_enabled_extension("query"): |
|
454 |
+ result = self.query_extensions() |
|
455 |
+ elif is_enabled_extension("list-extended@putty.projects.tartarus.org"): |
|
456 |
+ result = self.request_identities(list_extended=True) |
|
457 |
+ else: |
|
458 |
+ raise ValueError(self._UNSUPPORTED_REQUEST) |
|
459 |
+ self.send_to_client.extend( |
|
460 |
+ ssh_agent.SSHAgentClient.string(bytes(result)) |
|
461 |
+ ) |
|
462 |
+ |
|
463 |
+ def query_extensions(self) -> Iterator[int]: |
|
464 |
+ """Answer an `SSH_AGENTC_EXTENSION` request. |
|
465 |
+ |
|
466 |
+ Yields: |
|
467 |
+ The bytes payload of the response, without the protocol |
|
468 |
+ framing. The payload is yielded byte by byte, as an |
|
469 |
+ iterable of 8-bit integers. |
|
470 |
+ |
|
471 |
+ """ |
|
472 |
+ yield _types.SSH_AGENT.EXTENSION_RESPONSE.value |
|
473 |
+ yield from ssh_agent.SSHAgentClient.string(b"query") |
|
474 |
+ extension_answers = [ |
|
475 |
+ b"query", |
|
476 |
+ b"list-extended@putty.projects.tartarus.org", |
|
477 |
+ ] |
|
478 |
+ for a in extension_answers: |
|
479 |
+ yield from ssh_agent.SSHAgentClient.string(a) |
|
480 |
+ |
|
481 |
+ def request_identities( |
|
482 |
+ self, *, list_extended: bool = False |
|
483 |
+ ) -> Iterator[int]: |
|
484 |
+ """Answer an `SSH_AGENTC_REQUEST_IDENTITIES` request. |
|
485 |
+ |
|
486 |
+ Args: |
|
487 |
+ list_extended: |
|
488 |
+ If true, answer an `SSH_AGENTC_EXTENSION` request for |
|
489 |
+ the `list-extended@putty.projects.tartarus.org` |
|
490 |
+ extension. Otherwise, answer an |
|
491 |
+ `SSH_AGENTC_REQUEST_IDENTITIES` request. |
|
492 |
+ |
|
493 |
+ Yields: |
|
494 |
+ The bytes payload of the response, without the protocol |
|
495 |
+ framing. The payload is yielded byte by byte, as an |
|
496 |
+ iterable of 8-bit integers. |
|
497 |
+ |
|
498 |
+ """ |
|
499 |
+ if list_extended: |
|
500 |
+ yield _types.SSH_AGENT.SUCCESS.value |
|
501 |
+ else: |
|
502 |
+ yield _types.SSH_AGENT.IDENTITIES_ANSWER.value |
|
503 |
+ signature_classes = [ |
|
504 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.SPEC, |
|
505 |
+ ] |
|
506 |
+ if ( |
|
507 |
+ "list-extended@putty.projects.tartarus.org" |
|
508 |
+ in self.enabled_extensions |
|
509 |
+ ): |
|
510 |
+ signature_classes.append( |
|
511 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979 |
|
512 |
+ ) |
|
513 |
+ keys = [ |
|
514 |
+ v |
|
515 |
+ for v in tests.data.ALL_KEYS.values() |
|
516 |
+ if any(cls in v.expected_signatures for cls in signature_classes) |
|
517 |
+ ] |
|
518 |
+ yield from ssh_agent.SSHAgentClient.uint32(len(keys)) |
|
519 |
+ for key in keys: |
|
520 |
+ yield from ssh_agent.SSHAgentClient.string(key.public_key_data) |
|
521 |
+ yield from ssh_agent.SSHAgentClient.string( |
|
522 |
+ b"test key without passphrase" |
|
523 |
+ ) |
|
524 |
+ if list_extended: |
|
525 |
+ yield from ssh_agent.SSHAgentClient.string( |
|
526 |
+ ssh_agent.SSHAgentClient.uint32(0) |
|
527 |
+ ) |
|
528 |
+ |
|
529 |
+ def sign(self) -> bytes: |
|
530 |
+ """Answer an `SSH_AGENTC_SIGN_REQUEST` request. |
|
531 |
+ |
|
532 |
+ Returns: |
|
533 |
+ The bytes payload of the response, without the protocol |
|
534 |
+ framing. |
|
535 |
+ |
|
536 |
+ """ |
|
537 |
+ try_rfc6979 = ( |
|
538 |
+ "list-extended@putty.projects.tartarus.org" |
|
539 |
+ in self.enabled_extensions |
|
540 |
+ ) |
|
541 |
+ spec = tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
542 |
+ rfc6979 = tests.data.SSHTestKeyDeterministicSignatureClass.RFC_6979 |
|
543 |
+ key_blob, rest = ssh_agent.SSHAgentClient.unstring_prefix( |
|
544 |
+ self.receive_from_client[self.HEADER_SIZE + self.CODE_SIZE :] |
|
545 |
+ ) |
|
546 |
+ sign_data, rest = ssh_agent.SSHAgentClient.unstring_prefix(rest) |
|
547 |
+ if len(rest) != 4: |
|
548 |
+ raise ValueError(self._INVALID_REQUEST) |
|
549 |
+ flags = int.from_bytes(rest, "big") |
|
550 |
+ if flags: |
|
551 |
+ raise ValueError(self._UNSUPPORTED_REQUEST) |
|
552 |
+ if sign_data != vault.Vault.UUID: |
|
553 |
+ raise ValueError(self._UNSUPPORTED_REQUEST) |
|
554 |
+ for key in tests.data.ALL_KEYS.values(): |
|
555 |
+ if key.public_key_data == key_blob: |
|
556 |
+ if spec in key.expected_signatures: |
|
557 |
+ return int.to_bytes( |
|
558 |
+ _types.SSH_AGENT.SIGN_RESPONSE.value, 1, "big" |
|
559 |
+ ) + ssh_agent.SSHAgentClient.string( |
|
560 |
+ key.expected_signatures[spec].signature |
|
561 |
+ ) |
|
562 |
+ if try_rfc6979 and rfc6979 in key.expected_signatures: |
|
563 |
+ return int.to_bytes( |
|
564 |
+ _types.SSH_AGENT.SIGN_RESPONSE.value, 1, "big" |
|
565 |
+ ) + ssh_agent.SSHAgentClient.string( |
|
566 |
+ key.expected_signatures[rfc6979].signature |
|
567 |
+ ) |
|
568 |
+ raise ValueError(self._UNSUPPORTED_REQUEST) |
|
569 |
+ raise ValueError(self._UNSUPPORTED_REQUEST) |
|
570 |
+ |
|
571 |
+ |
|
572 |
+# Standard variant |
|
573 |
+# ---------------- |
|
574 |
+ |
|
575 |
+ |
|
576 |
+@socketprovider.SocketProvider.register("stub_with_address") |
|
577 |
+class StubbedSSHAgentSocketWithAddress(StubbedSSHAgentSocket): |
|
578 |
+ """A [`StubbedSSHAgentSocket`][] requiring a specific address.""" |
|
579 |
+ |
|
580 |
+ ADDRESS = "stub-ssh-agent:" |
|
581 |
+ """The correct address for connecting to this stubbed agent.""" |
|
582 |
+ |
|
583 |
+ def __init__(self, *extensions: str) -> None: |
|
584 |
+ """Initialize the agent, based on `SSH_AUTH_SOCK`. |
|
585 |
+ |
|
586 |
+ Socket addresses of the form `stub-ssh-agent:<errno_value>` will |
|
587 |
+ raise an [`OSError`][] (or the respective subclass) with the |
|
588 |
+ specified [`errno`][] value. For example, |
|
589 |
+ `stub-ssh-agent:EPERM` will raise a [`PermissionError`][]. |
|
590 |
+ |
|
591 |
+ Raises: |
|
592 |
+ KeyError: |
|
593 |
+ The `SSH_AUTH_SOCK` environment variable is not set. |
|
594 |
+ OSError: |
|
595 |
+ The address in `SSH_AUTH_SOCK` is unsuited. |
|
596 |
+ |
|
597 |
+ """ |
|
598 |
+ super().__init__(*extensions) |
|
599 |
+ try: |
|
600 |
+ orig_address = os.environ["SSH_AUTH_SOCK"] |
|
601 |
+ except KeyError as exc: |
|
602 |
+ msg = "SSH_AUTH_SOCK environment variable" |
|
603 |
+ raise KeyError(msg) from exc |
|
604 |
+ address = orig_address |
|
605 |
+ if not address.startswith(self.ADDRESS): |
|
606 |
+ address = self.ADDRESS + "ENOENT" |
|
607 |
+ errcode = address.removeprefix(self.ADDRESS) |
|
608 |
+ if errcode and not ( |
|
609 |
+ errcode.startswith("E") and hasattr(errno, errcode) |
|
610 |
+ ): |
|
611 |
+ errcode = "EINVAL" |
|
612 |
+ if errcode: |
|
613 |
+ errno_val = getattr(errno, errcode) |
|
614 |
+ raise OSError(errno_val, os.strerror(errno_val), orig_address) |
|
615 |
+ |
|
616 |
+ |
|
617 |
+# Deterministic variant |
|
618 |
+# --------------------- |
|
619 |
+ |
|
620 |
+ |
|
621 |
+@socketprovider.SocketProvider.register( |
|
622 |
+ "stub_with_address_and_deterministic_dsa" |
|
623 |
+) |
|
624 |
+class StubbedSSHAgentSocketWithAddressAndDeterministicDSA( |
|
625 |
+ StubbedSSHAgentSocketWithAddress |
|
626 |
+): |
|
627 |
+ """A [`StubbedSSHAgentSocketWithAddress`][] supporting deterministic DSA.""" |
|
628 |
+ |
|
629 |
+ def __init__(self) -> None: |
|
630 |
+ """Initialize the agent. |
|
631 |
+ |
|
632 |
+ Set the supported extensions, and try issuing RFC 6979 and |
|
633 |
+ Pageant 0.68–0.80 DSA/ECDSA signatures, if possible. See the |
|
634 |
+ [superclass constructor][StubbedSSHAgentSocketWithAddress] for |
|
635 |
+ other details. |
|
636 |
+ |
|
637 |
+ Raises: |
|
638 |
+ KeyError: See superclass. |
|
639 |
+ OSError: See superclass. |
|
640 |
+ |
|
641 |
+ """ # noqa: RUF002 |
|
642 |
+ super().__init__("query", "list-extended@putty.projects.tartarus.org") |
|
643 |
+ self.try_rfc6979 = True |
|
644 |
+ self.try_pageant_068_080 = True |
... | ... |
@@ -0,0 +1,168 @@ |
1 |
+# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: Zlib |
|
4 |
+ |
|
5 |
+"""`hypothesis` testing machinery for the `derivepassphrase` test suite. |
|
6 |
+ |
|
7 |
+This is all the `hypothesis`-specific data and functionality used in the |
|
8 |
+`derivepassphrase` test suite; this includes custom `hypothesis` |
|
9 |
+strategies, or state machines, or state machine helper functions, or |
|
10 |
+functions interacting with the `hypothesis` settings. |
|
11 |
+ |
|
12 |
+All similar-minded code requiring only plain `pytest` lives in [the |
|
13 |
+`pytest` sibling module][tests.machinery.pytest]. |
|
14 |
+ |
|
15 |
+""" |
|
16 |
+ |
|
17 |
+from __future__ import annotations |
|
18 |
+ |
|
19 |
+import copy |
|
20 |
+from typing import TYPE_CHECKING |
|
21 |
+ |
|
22 |
+import hypothesis |
|
23 |
+from hypothesis import strategies |
|
24 |
+ |
|
25 |
+import tests.data |
|
26 |
+import tests.machinery |
|
27 |
+from derivepassphrase import _types |
|
28 |
+ |
|
29 |
+__all__ = () |
|
30 |
+ |
|
31 |
+if TYPE_CHECKING: |
|
32 |
+ from typing_extensions import Any |
|
33 |
+ |
|
34 |
+ |
|
35 |
+# Hypothesis settings management |
|
36 |
+# ============================== |
|
37 |
+ |
|
38 |
+ |
|
39 |
+def get_concurrency_step_count( |
|
40 |
+ settings: hypothesis.settings | None = None, |
|
41 |
+) -> int: |
|
42 |
+ """Return the desired step count for concurrency-related tests. |
|
43 |
+ |
|
44 |
+ This is the smaller of the [general concurrency |
|
45 |
+ limit][tests.machinery.get_concurrency_limit] and the step count |
|
46 |
+ from the current hypothesis settings. |
|
47 |
+ |
|
48 |
+ Args: |
|
49 |
+ settings: |
|
50 |
+ The hypothesis settings for a specific tests. If not given, |
|
51 |
+ then the current profile will be queried directly. |
|
52 |
+ |
|
53 |
+ """ |
|
54 |
+ if settings is None: # pragma: no cover |
|
55 |
+ settings = hypothesis.settings() |
|
56 |
+ return min(tests.machinery.get_concurrency_limit(), settings.stateful_step_count) |
|
57 |
+ |
|
58 |
+ |
|
59 |
+# Hypothesis strategies |
|
60 |
+# ===================== |
|
61 |
+ |
|
62 |
+ |
|
63 |
+@strategies.composite |
|
64 |
+def vault_full_service_config(draw: strategies.DrawFn) -> dict[str, int]: |
|
65 |
+ """Hypothesis strategy for full vault service configurations. |
|
66 |
+ |
|
67 |
+ Returns a sample configuration with restrictions on length, repeat |
|
68 |
+ count, and all character classes, while ensuring the settings are |
|
69 |
+ not obviously unsatisfiable. |
|
70 |
+ |
|
71 |
+ Args: |
|
72 |
+ draw: |
|
73 |
+ The `draw` function, as provided for by hypothesis. |
|
74 |
+ |
|
75 |
+ """ |
|
76 |
+ repeat = draw(strategies.integers(min_value=0, max_value=10)) |
|
77 |
+ lower = draw(strategies.integers(min_value=0, max_value=10)) |
|
78 |
+ upper = draw(strategies.integers(min_value=0, max_value=10)) |
|
79 |
+ number = draw(strategies.integers(min_value=0, max_value=10)) |
|
80 |
+ space = draw(strategies.integers(min_value=0, max_value=repeat)) |
|
81 |
+ dash = draw(strategies.integers(min_value=0, max_value=10)) |
|
82 |
+ symbol = draw(strategies.integers(min_value=0, max_value=10)) |
|
83 |
+ length = draw( |
|
84 |
+ strategies.integers( |
|
85 |
+ min_value=max(1, lower + upper + number + space + dash + symbol), |
|
86 |
+ max_value=70, |
|
87 |
+ ) |
|
88 |
+ ) |
|
89 |
+ hypothesis.assume(lower + upper + number + dash + symbol > 0) |
|
90 |
+ hypothesis.assume(lower + upper + number + space + symbol > 0) |
|
91 |
+ hypothesis.assume(repeat >= space) |
|
92 |
+ return { |
|
93 |
+ "lower": lower, |
|
94 |
+ "upper": upper, |
|
95 |
+ "number": number, |
|
96 |
+ "space": space, |
|
97 |
+ "dash": dash, |
|
98 |
+ "symbol": symbol, |
|
99 |
+ "repeat": repeat, |
|
100 |
+ "length": length, |
|
101 |
+ } |
|
102 |
+ |
|
103 |
+ |
|
104 |
+@strategies.composite |
|
105 |
+def smudged_vault_test_config( |
|
106 |
+ draw: strategies.DrawFn, |
|
107 |
+ config: Any = strategies.sampled_from(tests.data.TEST_CONFIGS).filter( # noqa: B008 |
|
108 |
+ tests.data.is_smudgable_vault_test_config |
|
109 |
+ ), |
|
110 |
+) -> Any: |
|
111 |
+ """Hypothesis strategy to replace falsy values with other falsy values. |
|
112 |
+ |
|
113 |
+ Uses [`_types.js_truthiness`][] internally, which is tested |
|
114 |
+ separately by |
|
115 |
+ [`tests.test_derivepassphrase_types.test_100_js_truthiness`][]. |
|
116 |
+ |
|
117 |
+ Args: |
|
118 |
+ draw: |
|
119 |
+ The `draw` function, as provided for by hypothesis. |
|
120 |
+ config: |
|
121 |
+ A strategy which generates [`VaultTestConfig`][] objects. |
|
122 |
+ |
|
123 |
+ Returns: |
|
124 |
+ A new [`VaultTestConfig`][] where some falsy values have been |
|
125 |
+ replaced or added. |
|
126 |
+ |
|
127 |
+ """ |
|
128 |
+ |
|
129 |
+ falsy = (None, False, 0, 0.0, "", float("nan")) |
|
130 |
+ falsy_no_str = (None, False, 0, 0.0, float("nan")) |
|
131 |
+ falsy_no_zero = (None, False, "", float("nan")) |
|
132 |
+ conf = draw(config) |
|
133 |
+ hypothesis.assume(tests.data.is_smudgable_vault_test_config(conf)) |
|
134 |
+ obj = copy.deepcopy(conf.config) |
|
135 |
+ services: list[dict[str, Any]] = list(obj["services"].values()) |
|
136 |
+ if "global" in obj: |
|
137 |
+ services.append(obj["global"]) |
|
138 |
+ assert all(isinstance(x, dict) for x in services), ( |
|
139 |
+ "is_smudgable_vault_test_config guard failed to " |
|
140 |
+ "ensure each settings dict is a dict" |
|
141 |
+ ) |
|
142 |
+ for service in services: |
|
143 |
+ for key in ("phrase",): |
|
144 |
+ value = service.get(key) |
|
145 |
+ if not _types.js_truthiness(value) and value != "": |
|
146 |
+ service[key] = draw(strategies.sampled_from(falsy_no_str)) |
|
147 |
+ for key in ( |
|
148 |
+ "notes", |
|
149 |
+ "key", |
|
150 |
+ "length", |
|
151 |
+ "repeat", |
|
152 |
+ ): |
|
153 |
+ value = service.get(key) |
|
154 |
+ if not _types.js_truthiness(value): |
|
155 |
+ service[key] = draw(strategies.sampled_from(falsy)) |
|
156 |
+ for key in ( |
|
157 |
+ "lower", |
|
158 |
+ "upper", |
|
159 |
+ "number", |
|
160 |
+ "space", |
|
161 |
+ "dash", |
|
162 |
+ "symbol", |
|
163 |
+ ): |
|
164 |
+ value = service.get(key) |
|
165 |
+ if not _types.js_truthiness(value) and value != 0: |
|
166 |
+ service[key] = draw(strategies.sampled_from(falsy_no_zero)) |
|
167 |
+ hypothesis.assume(obj != conf.config) |
|
168 |
+ return tests.data.VaultTestConfig(obj, conf.comment, conf.validation_settings) |
... | ... |
@@ -0,0 +1,514 @@ |
1 |
+# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: Zlib |
|
4 |
+ |
|
5 |
+"""`pytest` testing machinery for the `derivepassphrase` test suite. |
|
6 |
+ |
|
7 |
+This is all the `pytest`-specific data and functionality used in the |
|
8 |
+`derivepassphrase` test suite; this includes `pytest` marks and |
|
9 |
+monkeypatched functions (or functions relying heavily internally on |
|
10 |
+monkeypatching). |
|
11 |
+ |
|
12 |
+All code requiring *more* than plain `pytest` lives in its own sibling |
|
13 |
+module, e.g., the `hypothesis`-related stuff lives in the [`hypothesis` |
|
14 |
+sibling module][tests.machinery.hypothesis]. |
|
15 |
+ |
|
16 |
+""" |
|
17 |
+ |
|
18 |
+from __future__ import annotations |
|
19 |
+ |
|
20 |
+import base64 |
|
21 |
+import contextlib |
|
22 |
+import importlib.util |
|
23 |
+import json |
|
24 |
+import os |
|
25 |
+import pathlib |
|
26 |
+import sys |
|
27 |
+import tempfile |
|
28 |
+import types |
|
29 |
+import zipfile |
|
30 |
+from typing import TYPE_CHECKING |
|
31 |
+ |
|
32 |
+import pytest |
|
33 |
+from typing_extensions import assert_never, overload |
|
34 |
+ |
|
35 |
+import tests.data |
|
36 |
+import tests.machinery |
|
37 |
+from derivepassphrase._internals import cli_helpers, cli_machinery |
|
38 |
+from derivepassphrase.ssh_agent import socketprovider |
|
39 |
+ |
|
40 |
+__all__ = () |
|
41 |
+ |
|
42 |
+if TYPE_CHECKING: |
|
43 |
+ from collections.abc import Callable, Iterator, Sequence |
|
44 |
+ from contextlib import AbstractContextManager |
|
45 |
+ |
|
46 |
+ from typing_extensions import Any |
|
47 |
+ |
|
48 |
+ |
|
49 |
+# Marks |
|
50 |
+# ===== |
|
51 |
+ |
|
52 |
+ |
|
53 |
+skip_if_cryptography_support = pytest.mark.skipif( |
|
54 |
+ importlib.util.find_spec("cryptography") is not None, |
|
55 |
+ reason='cryptography support available; cannot test "no support" scenario', |
|
56 |
+) |
|
57 |
+""" |
|
58 |
+A cached pytest mark to skip this test if cryptography support is |
|
59 |
+available. Usually this means that the test targets |
|
60 |
+`derivepassphrase`'s fallback functionality, which is not available |
|
61 |
+whenever the primary functionality is. |
|
62 |
+""" |
|
63 |
+skip_if_no_cryptography_support = pytest.mark.skipif( |
|
64 |
+ importlib.util.find_spec("cryptography") is None, |
|
65 |
+ reason='no "cryptography" support', |
|
66 |
+) |
|
67 |
+""" |
|
68 |
+A cached pytest mark to skip this test if cryptography support is not |
|
69 |
+available. Usually this means that the test targets the |
|
70 |
+`derivepassphrase export vault` subcommand, whose functionality depends |
|
71 |
+on cryptography support being available. |
|
72 |
+""" |
|
73 |
+skip_if_on_the_annoying_os = pytest.mark.skipif( |
|
74 |
+ sys.platform == "win32", |
|
75 |
+ reason="The Annoying OS behaves differently.", |
|
76 |
+) |
|
77 |
+""" |
|
78 |
+A cached pytest mark to skip this test if running on The Annoying |
|
79 |
+Operating System, a.k.a. Microsoft Windows. Usually this is due to |
|
80 |
+unnecessary and stupid differences in the OS internals, and these |
|
81 |
+differences are deemed irreconcilable in the context of the decorated |
|
82 |
+test, so the test is to be skipped. |
|
83 |
+ |
|
84 |
+See also: |
|
85 |
+ [`xfail_on_the_annoying_os`][] |
|
86 |
+ |
|
87 |
+""" |
|
88 |
+skip_if_no_multiprocessing_support = pytest.mark.skipif( |
|
89 |
+ importlib.util.find_spec("multiprocessing") is None, |
|
90 |
+ reason='no "multiprocessing" support', |
|
91 |
+) |
|
92 |
+""" |
|
93 |
+A cached pytest mark to skip this test if multiprocessing support is not |
|
94 |
+available. Usually this means that the test targets the concurrency |
|
95 |
+features of `derivepassphrase`, which is generally only possible to test |
|
96 |
+in separate processes because the testing machinery operates on |
|
97 |
+process-global state. |
|
98 |
+""" |
|
99 |
+ |
|
100 |
+ |
|
101 |
+def xfail_on_the_annoying_os( |
|
102 |
+ f: Callable | None = None, |
|
103 |
+ /, |
|
104 |
+ *, |
|
105 |
+ reason: str = "", |
|
106 |
+) -> pytest.MarkDecorator | Any: # pragma: no cover |
|
107 |
+ """Annotate a test which fails on The Annoying OS. |
|
108 |
+ |
|
109 |
+ Annotate a test to indicate that it fails on The Annoying Operating |
|
110 |
+ System, a.k.a. Microsoft Windows. Usually this is due to |
|
111 |
+ differences in the design of OS internals, and usually, these |
|
112 |
+ differences are both unnecessary and stupid. |
|
113 |
+ |
|
114 |
+ Args: |
|
115 |
+ f: |
|
116 |
+ A callable to decorate. If not given, return the pytest |
|
117 |
+ mark directly. |
|
118 |
+ reason: |
|
119 |
+ An optional, more detailed reason stating why this test |
|
120 |
+ fails on The Annoying OS. |
|
121 |
+ |
|
122 |
+ Returns: |
|
123 |
+ The callable, marked as an expected failure on the Annoying OS, |
|
124 |
+ or alternatively a suitable pytest mark if no callable was |
|
125 |
+ passed. The reason will begin with the phrase "The Annoying OS |
|
126 |
+ behaves differently.", and the optional detailed reason, if not |
|
127 |
+ empty, will follow. |
|
128 |
+ |
|
129 |
+ """ |
|
130 |
+ import hypothesis # noqa: PLC0415 |
|
131 |
+ |
|
132 |
+ base_reason = "The Annoying OS behaves differently." |
|
133 |
+ full_reason = base_reason if not reason else f"{base_reason} {reason}" |
|
134 |
+ mark = pytest.mark.xfail( |
|
135 |
+ sys.platform == "win32", |
|
136 |
+ reason=full_reason, |
|
137 |
+ raises=(AssertionError, hypothesis.errors.FailedHealthCheck), |
|
138 |
+ strict=True, |
|
139 |
+ ) |
|
140 |
+ return mark if f is None else mark(f) |
|
141 |
+ |
|
142 |
+ |
|
143 |
+# Parameter sets |
|
144 |
+# ============== |
|
145 |
+ |
|
146 |
+ |
|
147 |
+class Parametrize(types.SimpleNamespace): |
|
148 |
+ VAULT_CONFIG_FORMATS_DATA = pytest.mark.parametrize( |
|
149 |
+ ["config", "format", "config_data"], |
|
150 |
+ [ |
|
151 |
+ pytest.param( |
|
152 |
+ tests.data.VAULT_V02_CONFIG, |
|
153 |
+ "v0.2", |
|
154 |
+ tests.data.VAULT_V02_CONFIG_DATA, |
|
155 |
+ id="0.2", |
|
156 |
+ ), |
|
157 |
+ pytest.param( |
|
158 |
+ tests.data.VAULT_V03_CONFIG, |
|
159 |
+ "v0.3", |
|
160 |
+ tests.data.VAULT_V03_CONFIG_DATA, |
|
161 |
+ id="0.3", |
|
162 |
+ ), |
|
163 |
+ pytest.param( |
|
164 |
+ tests.data.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
165 |
+ "storeroom", |
|
166 |
+ tests.data.VAULT_STOREROOM_CONFIG_DATA, |
|
167 |
+ id="storeroom", |
|
168 |
+ ), |
|
169 |
+ ], |
|
170 |
+ ) |
|
171 |
+ |
|
172 |
+ |
|
173 |
+# Monkeypatchings |
|
174 |
+# =============== |
|
175 |
+ |
|
176 |
+ |
|
177 |
+@contextlib.contextmanager |
|
178 |
+def faked_entry_point_list( # noqa: C901 |
|
179 |
+ additional_entry_points: Sequence[importlib.metadata.EntryPoint], |
|
180 |
+ remove_conflicting_entries: bool = False, |
|
181 |
+) -> Iterator[Sequence[str]]: |
|
182 |
+ """Yield a context where additional entry points are visible. |
|
183 |
+ |
|
184 |
+ Args: |
|
185 |
+ additional_entry_points: |
|
186 |
+ A sequence of entry point objects that should additionally |
|
187 |
+ be visible. |
|
188 |
+ remove_conflicting_entries: |
|
189 |
+ If true, remove all names provided by the additional entry |
|
190 |
+ points, otherwise leave them untouched. |
|
191 |
+ |
|
192 |
+ Yields: |
|
193 |
+ A sequence of registry names that are newly available within the |
|
194 |
+ context. |
|
195 |
+ |
|
196 |
+ """ |
|
197 |
+ true_entry_points = importlib.metadata.entry_points() |
|
198 |
+ additional_entry_points = list(additional_entry_points) |
|
199 |
+ |
|
200 |
+ if sys.version_info >= (3, 12): |
|
201 |
+ new_entry_points = importlib.metadata.EntryPoints( |
|
202 |
+ list(true_entry_points) + additional_entry_points |
|
203 |
+ ) |
|
204 |
+ |
|
205 |
+ @overload |
|
206 |
+ def mangled_entry_points( |
|
207 |
+ *, group: None = None |
|
208 |
+ ) -> importlib.metadata.EntryPoints: ... |
|
209 |
+ |
|
210 |
+ @overload |
|
211 |
+ def mangled_entry_points( |
|
212 |
+ *, group: str |
|
213 |
+ ) -> importlib.metadata.EntryPoints: ... |
|
214 |
+ |
|
215 |
+ def mangled_entry_points( |
|
216 |
+ **params: Any, |
|
217 |
+ ) -> importlib.metadata.EntryPoints: |
|
218 |
+ return new_entry_points.select(**params) |
|
219 |
+ |
|
220 |
+ elif sys.version_info >= (3, 10): |
|
221 |
+ # Compatibility concerns within importlib.metadata: depending on |
|
222 |
+ # whether the .select() API is used, the result is either the dict |
|
223 |
+ # of groups of points (as in < 3.10), or the EntryPoints iterable |
|
224 |
+ # (as in >= 3.12). So our wrapper needs to duplicate that |
|
225 |
+ # interface. FUN. |
|
226 |
+ new_entry_points_dict = { |
|
227 |
+ k: list(v) for k, v in true_entry_points.items() |
|
228 |
+ } |
|
229 |
+ for ep in additional_entry_points: |
|
230 |
+ new_entry_points_dict.setdefault(ep.group, []).append(ep) |
|
231 |
+ new_entry_points = importlib.metadata.EntryPoints([ |
|
232 |
+ ep for group in new_entry_points_dict.values() for ep in group |
|
233 |
+ ]) |
|
234 |
+ |
|
235 |
+ @overload |
|
236 |
+ def mangled_entry_points( |
|
237 |
+ *, group: None = None |
|
238 |
+ ) -> dict[ |
|
239 |
+ str, |
|
240 |
+ list[importlib.metadata.EntryPoint] |
|
241 |
+ | tuple[importlib.metadata.EntryPoint, ...], |
|
242 |
+ ]: ... |
|
243 |
+ |
|
244 |
+ @overload |
|
245 |
+ def mangled_entry_points( |
|
246 |
+ *, group: str |
|
247 |
+ ) -> importlib.metadata.EntryPoints: ... |
|
248 |
+ |
|
249 |
+ def mangled_entry_points( |
|
250 |
+ **params: Any, |
|
251 |
+ ) -> ( |
|
252 |
+ importlib.metadata.EntryPoints |
|
253 |
+ | dict[ |
|
254 |
+ str, |
|
255 |
+ list[importlib.metadata.EntryPoint] |
|
256 |
+ | tuple[importlib.metadata.EntryPoint, ...], |
|
257 |
+ ] |
|
258 |
+ ): |
|
259 |
+ return ( |
|
260 |
+ new_entry_points.select(**params) |
|
261 |
+ if params |
|
262 |
+ else new_entry_points_dict |
|
263 |
+ ) |
|
264 |
+ |
|
265 |
+ else: |
|
266 |
+ new_entry_points: dict[ |
|
267 |
+ str, |
|
268 |
+ list[importlib.metadata.EntryPoint] |
|
269 |
+ | tuple[importlib.metadata.EntryPoint, ...], |
|
270 |
+ ] = { |
|
271 |
+ group_name: list(group) |
|
272 |
+ for group_name, group in true_entry_points.items() |
|
273 |
+ } |
|
274 |
+ for ep in additional_entry_points: |
|
275 |
+ new_entry_points.setdefault(ep.group, []) |
|
276 |
+ new_entry_points[ep.group].append(ep) |
|
277 |
+ new_entry_points = { |
|
278 |
+ group_name: tuple(group) |
|
279 |
+ for group_name, group in new_entry_points.items() |
|
280 |
+ } |
|
281 |
+ |
|
282 |
+ @overload |
|
283 |
+ def mangled_entry_points( |
|
284 |
+ *, group: None = None |
|
285 |
+ ) -> dict[str, tuple[importlib.metadata.EntryPoint, ...]]: ... |
|
286 |
+ |
|
287 |
+ @overload |
|
288 |
+ def mangled_entry_points( |
|
289 |
+ *, group: str |
|
290 |
+ ) -> tuple[importlib.metadata.EntryPoint, ...]: ... |
|
291 |
+ |
|
292 |
+ def mangled_entry_points( |
|
293 |
+ *, group: str | None = None |
|
294 |
+ ) -> ( |
|
295 |
+ dict[str, tuple[importlib.metadata.EntryPoint, ...]] |
|
296 |
+ | tuple[importlib.metadata.EntryPoint, ...] |
|
297 |
+ ): |
|
298 |
+ return ( |
|
299 |
+ new_entry_points.get(group, ()) |
|
300 |
+ if group is not None |
|
301 |
+ else new_entry_points |
|
302 |
+ ) |
|
303 |
+ |
|
304 |
+ registry = socketprovider.SocketProvider.registry |
|
305 |
+ new_registry = registry.copy() |
|
306 |
+ keys = [ep.load().key for ep in additional_entry_points] |
|
307 |
+ aliases = [a for ep in additional_entry_points for a in ep.load().aliases] |
|
308 |
+ if remove_conflicting_entries: # pragma: no cover [unused] |
|
309 |
+ for name in [*keys, *aliases]: |
|
310 |
+ new_registry.pop(name, None) |
|
311 |
+ |
|
312 |
+ with pytest.MonkeyPatch.context() as monkeypatch: |
|
313 |
+ monkeypatch.setattr( |
|
314 |
+ socketprovider.SocketProvider, "registry", new_registry |
|
315 |
+ ) |
|
316 |
+ monkeypatch.setattr( |
|
317 |
+ importlib.metadata, "entry_points", mangled_entry_points |
|
318 |
+ ) |
|
319 |
+ yield (*keys, *aliases) |
|
320 |
+ |
|
321 |
+ |
|
322 |
+@contextlib.contextmanager |
|
323 |
+def isolated_config( |
|
324 |
+ monkeypatch: pytest.MonkeyPatch, |
|
325 |
+ runner: tests.machinery.CliRunner, |
|
326 |
+ main_config_str: str | None = None, |
|
327 |
+) -> Iterator[None]: |
|
328 |
+ """Provide an isolated configuration setup, as a context. |
|
329 |
+ |
|
330 |
+ This context manager sets up (and changes into) a temporary |
|
331 |
+ directory, which holds the user configuration specified in |
|
332 |
+ `main_config_str`, if any. The manager also ensures that the |
|
333 |
+ environment variables `HOME` and `USERPROFILE` are set, and that |
|
334 |
+ `DERIVEPASSPHRASE_PATH` is unset. Upon exiting the context, the |
|
335 |
+ changes are undone and the temporary directory is removed. |
|
336 |
+ |
|
337 |
+ Args: |
|
338 |
+ monkeypatch: |
|
339 |
+ A monkeypatch fixture object. |
|
340 |
+ runner: |
|
341 |
+ A `click` CLI runner harness. |
|
342 |
+ main_config_str: |
|
343 |
+ Optional TOML file contents, to be used as the user |
|
344 |
+ configuration. |
|
345 |
+ |
|
346 |
+ Returns: |
|
347 |
+ A context manager, without a return value. |
|
348 |
+ |
|
349 |
+ """ |
|
350 |
+ prog_name = cli_helpers.PROG_NAME |
|
351 |
+ env_name = prog_name.replace(" ", "_").upper() + "_PATH" |
|
352 |
+ # TODO(the-13th-letter): Rewrite using parenthesized with-statements. |
|
353 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
354 |
+ with contextlib.ExitStack() as stack: |
|
355 |
+ stack.enter_context(runner.isolated_filesystem()) |
|
356 |
+ stack.enter_context( |
|
357 |
+ cli_machinery.StandardCLILogging.ensure_standard_logging() |
|
358 |
+ ) |
|
359 |
+ stack.enter_context( |
|
360 |
+ cli_machinery.StandardCLILogging.ensure_standard_warnings_logging() |
|
361 |
+ ) |
|
362 |
+ cwd = str(pathlib.Path.cwd().resolve()) |
|
363 |
+ monkeypatch.setenv("HOME", cwd) |
|
364 |
+ monkeypatch.setenv("APPDATA", cwd) |
|
365 |
+ monkeypatch.setenv("LOCALAPPDATA", cwd) |
|
366 |
+ monkeypatch.delenv(env_name, raising=False) |
|
367 |
+ config_dir = cli_helpers.config_filename(subsystem=None) |
|
368 |
+ config_dir.mkdir(parents=True, exist_ok=True) |
|
369 |
+ if isinstance(main_config_str, str): |
|
370 |
+ cli_helpers.config_filename("user configuration").write_text( |
|
371 |
+ main_config_str, encoding="UTF-8" |
|
372 |
+ ) |
|
373 |
+ try: |
|
374 |
+ yield |
|
375 |
+ finally: |
|
376 |
+ cli_helpers.config_filename("write lock").unlink(missing_ok=True) |
|
377 |
+ |
|
378 |
+ |
|
379 |
+@contextlib.contextmanager |
|
380 |
+def isolated_vault_config( |
|
381 |
+ monkeypatch: pytest.MonkeyPatch, |
|
382 |
+ runner: tests.machinery.CliRunner, |
|
383 |
+ vault_config: Any, |
|
384 |
+ main_config_str: str | None = None, |
|
385 |
+) -> Iterator[None]: |
|
386 |
+ """Provide an isolated vault configuration setup, as a context. |
|
387 |
+ |
|
388 |
+ Uses [`isolated_config`][] internally. Beyond those actions, this |
|
389 |
+ manager also loads the specified vault configuration into the |
|
390 |
+ context. |
|
391 |
+ |
|
392 |
+ Args: |
|
393 |
+ monkeypatch: |
|
394 |
+ A monkeypatch fixture object. |
|
395 |
+ runner: |
|
396 |
+ A `click` CLI runner harness. |
|
397 |
+ vault_config: |
|
398 |
+ A valid vault configuration, to be integrated into the |
|
399 |
+ context. |
|
400 |
+ main_config_str: |
|
401 |
+ Optional TOML file contents, to be used as the user |
|
402 |
+ configuration. |
|
403 |
+ |
|
404 |
+ Returns: |
|
405 |
+ A context manager, without a return value. |
|
406 |
+ |
|
407 |
+ """ |
|
408 |
+ with isolated_config( |
|
409 |
+ monkeypatch=monkeypatch, runner=runner, main_config_str=main_config_str |
|
410 |
+ ): |
|
411 |
+ config_filename = cli_helpers.config_filename(subsystem="vault") |
|
412 |
+ with config_filename.open("w", encoding="UTF-8") as outfile: |
|
413 |
+ json.dump(vault_config, outfile) |
|
414 |
+ yield |
|
415 |
+ |
|
416 |
+ |
|
417 |
+@contextlib.contextmanager |
|
418 |
+def isolated_vault_exporter_config( |
|
419 |
+ monkeypatch: pytest.MonkeyPatch, |
|
420 |
+ runner: tests.machinery.CliRunner, |
|
421 |
+ vault_config: str | bytes | None = None, |
|
422 |
+ vault_key: str | None = None, |
|
423 |
+) -> Iterator[None]: |
|
424 |
+ """Provide an isolated vault configuration setup, as a context. |
|
425 |
+ |
|
426 |
+ Works similarly to [`isolated_config`][], except that no user |
|
427 |
+ configuration is accepted or integrated into the context. This |
|
428 |
+ manager also accepts a serialized vault-native configuration and |
|
429 |
+ a vault encryption key to integrate into the context. |
|
430 |
+ |
|
431 |
+ Args: |
|
432 |
+ monkeypatch: |
|
433 |
+ A monkeypatch fixture object. |
|
434 |
+ runner: |
|
435 |
+ A `click` CLI runner harness. |
|
436 |
+ vault_config: |
|
437 |
+ An optional serialized vault-native configuration, to be |
|
438 |
+ integrated into the context. If a text string, then the |
|
439 |
+ contents are written to the file `.vault`. If a byte |
|
440 |
+ string, then it is treated as base64-encoded zip file |
|
441 |
+ contents, which---once inside the `.vault` directory---will |
|
442 |
+ be extracted into the current directory. |
|
443 |
+ vault_key: |
|
444 |
+ An optional encryption key presumably for the stored |
|
445 |
+ vault-native configuration. If given, then the environment |
|
446 |
+ variable `VAULT_KEY` will be populated with this key while |
|
447 |
+ the context is active. |
|
448 |
+ |
|
449 |
+ Returns: |
|
450 |
+ A context manager, without a return value. |
|
451 |
+ |
|
452 |
+ """ |
|
453 |
+ # TODO(the-13th-letter): Remove the fallback implementation. |
|
454 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.10 |
|
455 |
+ if TYPE_CHECKING: |
|
456 |
+ chdir: Callable[..., AbstractContextManager] |
|
457 |
+ else: |
|
458 |
+ try: |
|
459 |
+ chdir = contextlib.chdir # type: ignore[attr] |
|
460 |
+ except AttributeError: |
|
461 |
+ |
|
462 |
+ @contextlib.contextmanager |
|
463 |
+ def chdir( |
|
464 |
+ newpath: str | bytes | os.PathLike, |
|
465 |
+ ) -> Iterator[None]: # pragma: no branch |
|
466 |
+ oldpath = pathlib.Path.cwd().resolve() |
|
467 |
+ os.chdir(newpath) |
|
468 |
+ yield |
|
469 |
+ os.chdir(oldpath) |
|
470 |
+ |
|
471 |
+ with runner.isolated_filesystem(): |
|
472 |
+ cwd = str(pathlib.Path.cwd().resolve()) |
|
473 |
+ monkeypatch.setenv("HOME", cwd) |
|
474 |
+ monkeypatch.setenv("USERPROFILE", cwd) |
|
475 |
+ monkeypatch.delenv( |
|
476 |
+ cli_helpers.PROG_NAME.replace(" ", "_").upper() + "_PATH", |
|
477 |
+ raising=False, |
|
478 |
+ ) |
|
479 |
+ monkeypatch.delenv("VAULT_PATH", raising=False) |
|
480 |
+ monkeypatch.delenv("VAULT_KEY", raising=False) |
|
481 |
+ monkeypatch.delenv("LOGNAME", raising=False) |
|
482 |
+ monkeypatch.delenv("USER", raising=False) |
|
483 |
+ monkeypatch.delenv("USERNAME", raising=False) |
|
484 |
+ if vault_key is not None: |
|
485 |
+ monkeypatch.setenv("VAULT_KEY", vault_key) |
|
486 |
+ vault_config_path = pathlib.Path(".vault").resolve() |
|
487 |
+ # TODO(the-13th-letter): Rewrite using structural pattern matching. |
|
488 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
489 |
+ if isinstance(vault_config, str): |
|
490 |
+ vault_config_path.write_text(f"{vault_config}\n", encoding="UTF-8") |
|
491 |
+ elif isinstance(vault_config, bytes): |
|
492 |
+ vault_config_path.mkdir(parents=True, mode=0o700, exist_ok=True) |
|
493 |
+ # TODO(the-13th-letter): Rewrite using parenthesized |
|
494 |
+ # with-statements. |
|
495 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
496 |
+ with contextlib.ExitStack() as stack: |
|
497 |
+ stack.enter_context(chdir(vault_config_path)) |
|
498 |
+ tmpzipfile = stack.enter_context( |
|
499 |
+ tempfile.NamedTemporaryFile(suffix=".zip") |
|
500 |
+ ) |
|
501 |
+ for line in vault_config.splitlines(): |
|
502 |
+ tmpzipfile.write(base64.standard_b64decode(line)) |
|
503 |
+ tmpzipfile.flush() |
|
504 |
+ tmpzipfile.seek(0, 0) |
|
505 |
+ with zipfile.ZipFile(tmpzipfile.file) as zipfileobj: |
|
506 |
+ zipfileobj.extractall() |
|
507 |
+ elif vault_config is None: |
|
508 |
+ pass |
|
509 |
+ else: # pragma: no cover |
|
510 |
+ assert_never(vault_config) |
|
511 |
+ try: |
|
512 |
+ yield |
|
513 |
+ finally: |
|
514 |
+ cli_helpers.config_filename("write lock").unlink(missing_ok=True) |
... | ... |
@@ -8,7 +8,7 @@ import base64 |
8 | 8 |
|
9 | 9 |
import pytest |
10 | 10 |
|
11 |
-import tests |
|
11 |
+import tests.data |
|
12 | 12 |
from derivepassphrase import ssh_agent |
13 | 13 |
|
14 | 14 |
OPENSSH_MAGIC = b"openssh-key-v1\x00" |
... | ... |
@@ -138,14 +138,14 @@ class Parametrize: |
138 | 138 |
"""Common test parametrizations.""" |
139 | 139 |
|
140 | 140 |
TEST_KEYS = pytest.mark.parametrize( |
141 |
- ["keyname", "key"], tests.ALL_KEYS.items(), ids=tests.ALL_KEYS.keys() |
|
141 |
+ ["keyname", "key"], tests.data.ALL_KEYS.items(), ids=tests.data.ALL_KEYS.keys() |
|
142 | 142 |
) |
143 | 143 |
|
144 | 144 |
|
145 | 145 |
@Parametrize.TEST_KEYS |
146 | 146 |
def test_100_test_keys_public_keys_are_internally_consistent( |
147 | 147 |
keyname: str, |
148 |
- key: tests.SSHTestKey, |
|
148 |
+ key: tests.data.SSHTestKey, |
|
149 | 149 |
) -> None: |
150 | 150 |
"""The test key public key data structures are internally consistent.""" |
151 | 151 |
del keyname |
... | ... |
@@ -161,7 +161,7 @@ def test_100_test_keys_public_keys_are_internally_consistent( |
161 | 161 |
@Parametrize.TEST_KEYS |
162 | 162 |
def test_101_test_keys_private_keys_are_consistent_with_public_keys( |
163 | 163 |
keyname: str, |
164 |
- key: tests.SSHTestKey, |
|
164 |
+ key: tests.data.SSHTestKey, |
|
165 | 165 |
) -> None: |
166 | 166 |
"""The test key private key data are consistent with their public parts.""" |
167 | 167 |
del keyname |
... | ... |
@@ -195,7 +195,7 @@ def test_101_test_keys_private_keys_are_consistent_with_public_keys( |
195 | 195 |
@Parametrize.TEST_KEYS |
196 | 196 |
def test_102_test_keys_private_keys_are_internally_consistent( |
197 | 197 |
keyname: str, |
198 |
- key: tests.SSHTestKey, |
|
198 |
+ key: tests.data.SSHTestKey, |
|
199 | 199 |
) -> None: |
200 | 200 |
"""The test key private key data structures are internally consistent.""" |
201 | 201 |
del keyname |
... | ... |
@@ -34,7 +34,11 @@ import pytest |
34 | 34 |
from hypothesis import stateful, strategies |
35 | 35 |
from typing_extensions import Any, NamedTuple, TypeAlias |
36 | 36 |
|
37 |
-import tests |
|
37 |
+import tests.data |
|
38 |
+import tests.data.callables |
|
39 |
+import tests.machinery |
|
40 |
+import tests.machinery.hypothesis |
|
41 |
+import tests.machinery.pytest |
|
38 | 42 |
from derivepassphrase import _types, cli, ssh_agent, vault |
39 | 43 |
from derivepassphrase._internals import ( |
40 | 44 |
cli_helpers, |
... | ... |
@@ -51,22 +55,22 @@ if TYPE_CHECKING: |
51 | 55 |
|
52 | 56 |
from typing_extensions import Literal |
53 | 57 |
|
54 |
-DUMMY_SERVICE = tests.DUMMY_SERVICE |
|
55 |
-DUMMY_PASSPHRASE = tests.DUMMY_PASSPHRASE |
|
56 |
-DUMMY_CONFIG_SETTINGS = tests.DUMMY_CONFIG_SETTINGS |
|
57 |
-DUMMY_RESULT_PASSPHRASE = tests.DUMMY_RESULT_PASSPHRASE |
|
58 |
-DUMMY_RESULT_KEY1 = tests.DUMMY_RESULT_KEY1 |
|
59 |
-DUMMY_PHRASE_FROM_KEY1_RAW = tests.DUMMY_PHRASE_FROM_KEY1_RAW |
|
60 |
-DUMMY_PHRASE_FROM_KEY1 = tests.DUMMY_PHRASE_FROM_KEY1 |
|
58 |
+DUMMY_SERVICE = tests.data.DUMMY_SERVICE |
|
59 |
+DUMMY_PASSPHRASE = tests.data.DUMMY_PASSPHRASE |
|
60 |
+DUMMY_CONFIG_SETTINGS = tests.data.DUMMY_CONFIG_SETTINGS |
|
61 |
+DUMMY_RESULT_PASSPHRASE = tests.data.DUMMY_RESULT_PASSPHRASE |
|
62 |
+DUMMY_RESULT_KEY1 = tests.data.DUMMY_RESULT_KEY1 |
|
63 |
+DUMMY_PHRASE_FROM_KEY1_RAW = tests.data.DUMMY_PHRASE_FROM_KEY1_RAW |
|
64 |
+DUMMY_PHRASE_FROM_KEY1 = tests.data.DUMMY_PHRASE_FROM_KEY1 |
|
61 | 65 |
|
62 |
-DUMMY_KEY1 = tests.DUMMY_KEY1 |
|
63 |
-DUMMY_KEY1_B64 = tests.DUMMY_KEY1_B64 |
|
64 |
-DUMMY_KEY2 = tests.DUMMY_KEY2 |
|
65 |
-DUMMY_KEY2_B64 = tests.DUMMY_KEY2_B64 |
|
66 |
-DUMMY_KEY3 = tests.DUMMY_KEY3 |
|
67 |
-DUMMY_KEY3_B64 = tests.DUMMY_KEY3_B64 |
|
66 |
+DUMMY_KEY1 = tests.data.DUMMY_KEY1 |
|
67 |
+DUMMY_KEY1_B64 = tests.data.DUMMY_KEY1_B64 |
|
68 |
+DUMMY_KEY2 = tests.data.DUMMY_KEY2 |
|
69 |
+DUMMY_KEY2_B64 = tests.data.DUMMY_KEY2_B64 |
|
70 |
+DUMMY_KEY3 = tests.data.DUMMY_KEY3 |
|
71 |
+DUMMY_KEY3_B64 = tests.data.DUMMY_KEY3_B64 |
|
68 | 72 |
|
69 |
-TEST_CONFIGS = tests.TEST_CONFIGS |
|
73 |
+TEST_CONFIGS = tests.data.TEST_CONFIGS |
|
70 | 74 |
|
71 | 75 |
|
72 | 76 |
class IncompatibleConfiguration(NamedTuple): |
... | ... |
@@ -274,7 +278,7 @@ def is_harmless_config_import_warning(record: tuple[str, int, str]) -> bool: |
274 | 278 |
"because a key is also set:" |
275 | 279 |
), |
276 | 280 |
] |
277 |
- return any(tests.warning_emitted(w, [record]) for w in possible_warnings) |
|
281 |
+ return any(tests.machinery.warning_emitted(w, [record]) for w in possible_warnings) |
|
278 | 282 |
|
279 | 283 |
|
280 | 284 |
def assert_vault_config_is_indented_and_line_broken( |
... | ... |
@@ -323,8 +327,8 @@ def vault_config_exporter_shell_interpreter( # noqa: C901 |
323 | 327 |
*, |
324 | 328 |
prog_name_list: list[str] | None = None, |
325 | 329 |
command: click.BaseCommand | None = None, |
326 |
- runner: tests.CliRunner | None = None, |
|
327 |
-) -> Iterator[tests.ReadableResult]: |
|
330 |
+ runner: tests.machinery.CliRunner | None = None, |
|
331 |
+) -> Iterator[tests.machinery.ReadableResult]: |
|
328 | 332 |
"""A rudimentary sh(1) interpreter for `--export-as=sh` output. |
329 | 333 |
|
330 | 334 |
Assumes a script as emitted by `derivepassphrase vault |
... | ... |
@@ -341,7 +345,7 @@ def vault_config_exporter_shell_interpreter( # noqa: C901 |
341 | 345 |
if command is None: # pragma: no cover |
342 | 346 |
command = cli.derivepassphrase_vault |
343 | 347 |
if runner is None: # pragma: no cover |
344 |
- runner = tests.CliRunner(mix_stderr=False) |
|
348 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
345 | 349 |
n = len(prog_name_list) |
346 | 350 |
it = iter(script) |
347 | 351 |
while True: |
... | ... |
@@ -1218,7 +1222,7 @@ class Parametrize(types.SimpleNamespace): |
1218 | 1222 |
[ |
1219 | 1223 |
conf.config |
1220 | 1224 |
for conf in TEST_CONFIGS |
1221 |
- if tests.is_valid_test_config(conf) |
|
1225 |
+ if tests.data.is_valid_test_config(conf) |
|
1222 | 1226 |
], |
1223 | 1227 |
) |
1224 | 1228 |
KEY_OVERRIDING_IN_CONFIG = pytest.mark.parametrize( |
... | ... |
@@ -2015,14 +2019,14 @@ class TestAllCLI: |
2015 | 2019 |
TODO: Do we actually need this? What should we check for? |
2016 | 2020 |
|
2017 | 2021 |
""" |
2018 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2022 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2019 | 2023 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2020 | 2024 |
# with-statements. |
2021 | 2025 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2022 | 2026 |
with contextlib.ExitStack() as stack: |
2023 | 2027 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2024 | 2028 |
stack.enter_context( |
2025 |
- tests.isolated_config( |
|
2029 |
+ tests.machinery.pytest.isolated_config( |
|
2026 | 2030 |
monkeypatch=monkeypatch, |
2027 | 2031 |
runner=runner, |
2028 | 2032 |
) |
... | ... |
@@ -2044,14 +2048,14 @@ class TestAllCLI: |
2044 | 2048 |
TODO: Do we actually need this? What should we check for? |
2045 | 2049 |
|
2046 | 2050 |
""" |
2047 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2051 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2048 | 2052 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2049 | 2053 |
# with-statements. |
2050 | 2054 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2051 | 2055 |
with contextlib.ExitStack() as stack: |
2052 | 2056 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2053 | 2057 |
stack.enter_context( |
2054 |
- tests.isolated_config( |
|
2058 |
+ tests.machinery.pytest.isolated_config( |
|
2055 | 2059 |
monkeypatch=monkeypatch, |
2056 | 2060 |
runner=runner, |
2057 | 2061 |
) |
... | ... |
@@ -2075,14 +2079,14 @@ class TestAllCLI: |
2075 | 2079 |
TODO: Do we actually need this? What should we check for? |
2076 | 2080 |
|
2077 | 2081 |
""" |
2078 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2082 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2079 | 2083 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2080 | 2084 |
# with-statements. |
2081 | 2085 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2082 | 2086 |
with contextlib.ExitStack() as stack: |
2083 | 2087 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2084 | 2088 |
stack.enter_context( |
2085 |
- tests.isolated_config( |
|
2089 |
+ tests.machinery.pytest.isolated_config( |
|
2086 | 2090 |
monkeypatch=monkeypatch, |
2087 | 2091 |
runner=runner, |
2088 | 2092 |
) |
... | ... |
@@ -2106,14 +2110,14 @@ class TestAllCLI: |
2106 | 2110 |
TODO: Do we actually need this? What should we check for? |
2107 | 2111 |
|
2108 | 2112 |
""" |
2109 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2113 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2110 | 2114 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2111 | 2115 |
# with-statements. |
2112 | 2116 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2113 | 2117 |
with contextlib.ExitStack() as stack: |
2114 | 2118 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2115 | 2119 |
stack.enter_context( |
2116 |
- tests.isolated_config( |
|
2120 |
+ tests.machinery.pytest.isolated_config( |
|
2117 | 2121 |
monkeypatch=monkeypatch, |
2118 | 2122 |
runner=runner, |
2119 | 2123 |
) |
... | ... |
@@ -2139,14 +2143,14 @@ class TestAllCLI: |
2139 | 2143 |
non_eager_arguments: list[str], |
2140 | 2144 |
) -> None: |
2141 | 2145 |
"""Eager options terminate option and argument processing.""" |
2142 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2146 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2143 | 2147 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2144 | 2148 |
# with-statements. |
2145 | 2149 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2146 | 2150 |
with contextlib.ExitStack() as stack: |
2147 | 2151 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2148 | 2152 |
stack.enter_context( |
2149 |
- tests.isolated_config( |
|
2153 |
+ tests.machinery.pytest.isolated_config( |
|
2150 | 2154 |
monkeypatch=monkeypatch, |
2151 | 2155 |
runner=runner, |
2152 | 2156 |
) |
... | ... |
@@ -2174,14 +2178,14 @@ class TestAllCLI: |
2174 | 2178 |
|
2175 | 2179 |
""" |
2176 | 2180 |
color = False |
2177 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2181 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2178 | 2182 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2179 | 2183 |
# with-statements. |
2180 | 2184 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2181 | 2185 |
with contextlib.ExitStack() as stack: |
2182 | 2186 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2183 | 2187 |
stack.enter_context( |
2184 |
- tests.isolated_config( |
|
2188 |
+ tests.machinery.pytest.isolated_config( |
|
2185 | 2189 |
monkeypatch=monkeypatch, |
2186 | 2190 |
runner=runner, |
2187 | 2191 |
) |
... | ... |
@@ -2215,14 +2219,14 @@ class TestAllCLI: |
2215 | 2219 |
subcommands. |
2216 | 2220 |
|
2217 | 2221 |
""" |
2218 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2222 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2219 | 2223 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2220 | 2224 |
# with-statements. |
2221 | 2225 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2222 | 2226 |
with contextlib.ExitStack() as stack: |
2223 | 2227 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2224 | 2228 |
stack.enter_context( |
2225 |
- tests.isolated_config( |
|
2229 |
+ tests.machinery.pytest.isolated_config( |
|
2226 | 2230 |
monkeypatch=monkeypatch, |
2227 | 2231 |
runner=runner, |
2228 | 2232 |
) |
... | ... |
@@ -2256,14 +2260,14 @@ class TestAllCLI: |
2256 | 2260 |
of subcommands. |
2257 | 2261 |
|
2258 | 2262 |
""" |
2259 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2263 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2260 | 2264 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2261 | 2265 |
# with-statements. |
2262 | 2266 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2263 | 2267 |
with contextlib.ExitStack() as stack: |
2264 | 2268 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2265 | 2269 |
stack.enter_context( |
2266 |
- tests.isolated_config( |
|
2270 |
+ tests.machinery.pytest.isolated_config( |
|
2267 | 2271 |
monkeypatch=monkeypatch, |
2268 | 2272 |
runner=runner, |
2269 | 2273 |
) |
... | ... |
@@ -2304,14 +2308,14 @@ class TestAllCLI: |
2304 | 2308 |
configuration formats, and a list of available PEP 508 extras. |
2305 | 2309 |
|
2306 | 2310 |
""" |
2307 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2311 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2308 | 2312 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2309 | 2313 |
# with-statements. |
2310 | 2314 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2311 | 2315 |
with contextlib.ExitStack() as stack: |
2312 | 2316 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2313 | 2317 |
stack.enter_context( |
2314 |
- tests.isolated_config( |
|
2318 |
+ tests.machinery.pytest.isolated_config( |
|
2315 | 2319 |
monkeypatch=monkeypatch, |
2316 | 2320 |
runner=runner, |
2317 | 2321 |
) |
... | ... |
@@ -2359,14 +2363,14 @@ class TestAllCLI: |
2359 | 2363 |
first paragraph. |
2360 | 2364 |
|
2361 | 2365 |
""" |
2362 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2366 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2363 | 2367 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2364 | 2368 |
# with-statements. |
2365 | 2369 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2366 | 2370 |
with contextlib.ExitStack() as stack: |
2367 | 2371 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2368 | 2372 |
stack.enter_context( |
2369 |
- tests.isolated_config( |
|
2373 |
+ tests.machinery.pytest.isolated_config( |
|
2370 | 2374 |
monkeypatch=monkeypatch, |
2371 | 2375 |
runner=runner, |
2372 | 2376 |
) |
... | ... |
@@ -2411,14 +2415,14 @@ class TestCLI: |
2411 | 2415 |
self, |
2412 | 2416 |
) -> None: |
2413 | 2417 |
"""The `--help` option emits help text.""" |
2414 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2418 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2415 | 2419 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2416 | 2420 |
# with-statements. |
2417 | 2421 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2418 | 2422 |
with contextlib.ExitStack() as stack: |
2419 | 2423 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2420 | 2424 |
stack.enter_context( |
2421 |
- tests.isolated_config( |
|
2425 |
+ tests.machinery.pytest.isolated_config( |
|
2422 | 2426 |
monkeypatch=monkeypatch, |
2423 | 2427 |
runner=runner, |
2424 | 2428 |
) |
... | ... |
@@ -2441,14 +2445,14 @@ class TestCLI: |
2441 | 2445 |
self, |
2442 | 2446 |
) -> None: |
2443 | 2447 |
"""The `--version` option emits version information.""" |
2444 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2448 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2445 | 2449 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2446 | 2450 |
# with-statements. |
2447 | 2451 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2448 | 2452 |
with contextlib.ExitStack() as stack: |
2449 | 2453 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2450 | 2454 |
stack.enter_context( |
2451 |
- tests.isolated_config( |
|
2455 |
+ tests.machinery.pytest.isolated_config( |
|
2452 | 2456 |
monkeypatch=monkeypatch, |
2453 | 2457 |
runner=runner, |
2454 | 2458 |
) |
... | ... |
@@ -2473,20 +2477,20 @@ class TestCLI: |
2473 | 2477 |
"""Named character classes can be disabled on the command-line.""" |
2474 | 2478 |
option = f"--{charset_name}" |
2475 | 2479 |
charset = vault.Vault.CHARSETS[charset_name].decode("ascii") |
2476 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2480 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2477 | 2481 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2478 | 2482 |
# with-statements. |
2479 | 2483 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2480 | 2484 |
with contextlib.ExitStack() as stack: |
2481 | 2485 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2482 | 2486 |
stack.enter_context( |
2483 |
- tests.isolated_config( |
|
2487 |
+ tests.machinery.pytest.isolated_config( |
|
2484 | 2488 |
monkeypatch=monkeypatch, |
2485 | 2489 |
runner=runner, |
2486 | 2490 |
) |
2487 | 2491 |
) |
2488 | 2492 |
monkeypatch.setattr( |
2489 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
2493 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
2490 | 2494 |
) |
2491 | 2495 |
result = runner.invoke( |
2492 | 2496 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2504,20 +2508,20 @@ class TestCLI: |
2504 | 2508 |
self, |
2505 | 2509 |
) -> None: |
2506 | 2510 |
"""Character repetition can be disabled on the command-line.""" |
2507 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2511 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2508 | 2512 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2509 | 2513 |
# with-statements. |
2510 | 2514 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2511 | 2515 |
with contextlib.ExitStack() as stack: |
2512 | 2516 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2513 | 2517 |
stack.enter_context( |
2514 |
- tests.isolated_config( |
|
2518 |
+ tests.machinery.pytest.isolated_config( |
|
2515 | 2519 |
monkeypatch=monkeypatch, |
2516 | 2520 |
runner=runner, |
2517 | 2521 |
) |
2518 | 2522 |
) |
2519 | 2523 |
monkeypatch.setattr( |
2520 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
2524 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
2521 | 2525 |
) |
2522 | 2526 |
result = runner.invoke( |
2523 | 2527 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2538,26 +2542,26 @@ class TestCLI: |
2538 | 2542 |
@Parametrize.CONFIG_WITH_KEY |
2539 | 2543 |
def test_204a_key_from_config( |
2540 | 2544 |
self, |
2541 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
2545 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
2542 | 2546 |
config: _types.VaultConfig, |
2543 | 2547 |
) -> None: |
2544 | 2548 |
"""A stored configured SSH key will be used.""" |
2545 | 2549 |
del running_ssh_agent |
2546 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2550 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2547 | 2551 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2548 | 2552 |
# with-statements. |
2549 | 2553 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2550 | 2554 |
with contextlib.ExitStack() as stack: |
2551 | 2555 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2552 | 2556 |
stack.enter_context( |
2553 |
- tests.isolated_vault_config( |
|
2557 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2554 | 2558 |
monkeypatch=monkeypatch, |
2555 | 2559 |
runner=runner, |
2556 | 2560 |
vault_config=config, |
2557 | 2561 |
) |
2558 | 2562 |
) |
2559 | 2563 |
monkeypatch.setattr( |
2560 |
- vault.Vault, "phrase_from_key", tests.phrase_from_key |
|
2564 |
+ vault.Vault, "phrase_from_key", tests.data.callables.phrase_from_key |
|
2561 | 2565 |
) |
2562 | 2566 |
result = runner.invoke( |
2563 | 2567 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2578,18 +2582,18 @@ class TestCLI: |
2578 | 2582 |
|
2579 | 2583 |
def test_204b_key_from_command_line( |
2580 | 2584 |
self, |
2581 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
2585 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
2582 | 2586 |
) -> None: |
2583 | 2587 |
"""An SSH key requested on the command-line will be used.""" |
2584 | 2588 |
del running_ssh_agent |
2585 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2589 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2586 | 2590 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2587 | 2591 |
# with-statements. |
2588 | 2592 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2589 | 2593 |
with contextlib.ExitStack() as stack: |
2590 | 2594 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2591 | 2595 |
stack.enter_context( |
2592 |
- tests.isolated_vault_config( |
|
2596 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2593 | 2597 |
monkeypatch=monkeypatch, |
2594 | 2598 |
runner=runner, |
2595 | 2599 |
vault_config={ |
... | ... |
@@ -2598,10 +2602,10 @@ class TestCLI: |
2598 | 2602 |
) |
2599 | 2603 |
) |
2600 | 2604 |
monkeypatch.setattr( |
2601 |
- cli_helpers, "get_suitable_ssh_keys", tests.suitable_ssh_keys |
|
2605 |
+ cli_helpers, "get_suitable_ssh_keys", tests.data.callables.suitable_ssh_keys |
|
2602 | 2606 |
) |
2603 | 2607 |
monkeypatch.setattr( |
2604 |
- vault.Vault, "phrase_from_key", tests.phrase_from_key |
|
2608 |
+ vault.Vault, "phrase_from_key", tests.data.callables.phrase_from_key |
|
2605 | 2609 |
) |
2606 | 2610 |
result = runner.invoke( |
2607 | 2611 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2623,29 +2627,29 @@ class TestCLI: |
2623 | 2627 |
@Parametrize.KEY_INDEX |
2624 | 2628 |
def test_204c_key_override_on_command_line( |
2625 | 2629 |
self, |
2626 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
2630 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
2627 | 2631 |
config: dict[str, Any], |
2628 | 2632 |
key_index: int, |
2629 | 2633 |
) -> None: |
2630 | 2634 |
"""A command-line SSH key will override the configured key.""" |
2631 | 2635 |
del running_ssh_agent |
2632 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2636 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2633 | 2637 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2634 | 2638 |
# with-statements. |
2635 | 2639 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2636 | 2640 |
with contextlib.ExitStack() as stack: |
2637 | 2641 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2638 | 2642 |
stack.enter_context( |
2639 |
- tests.isolated_vault_config( |
|
2643 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2640 | 2644 |
monkeypatch=monkeypatch, |
2641 | 2645 |
runner=runner, |
2642 | 2646 |
vault_config=config, |
2643 | 2647 |
) |
2644 | 2648 |
) |
2645 | 2649 |
monkeypatch.setattr( |
2646 |
- ssh_agent.SSHAgentClient, "list_keys", tests.list_keys |
|
2650 |
+ ssh_agent.SSHAgentClient, "list_keys", tests.data.callables.list_keys |
|
2647 | 2651 |
) |
2648 |
- monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.sign) |
|
2652 |
+ monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.data.callables.sign) |
|
2649 | 2653 |
result = runner.invoke( |
2650 | 2654 |
cli.derivepassphrase_vault, |
2651 | 2655 |
["-k", "--", DUMMY_SERVICE], |
... | ... |
@@ -2660,18 +2664,18 @@ class TestCLI: |
2660 | 2664 |
|
2661 | 2665 |
def test_205_service_phrase_if_key_in_global_config( |
2662 | 2666 |
self, |
2663 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
2667 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
2664 | 2668 |
) -> None: |
2665 | 2669 |
"""A command-line passphrase will override the configured key.""" |
2666 | 2670 |
del running_ssh_agent |
2667 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2671 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2668 | 2672 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2669 | 2673 |
# with-statements. |
2670 | 2674 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2671 | 2675 |
with contextlib.ExitStack() as stack: |
2672 | 2676 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2673 | 2677 |
stack.enter_context( |
2674 |
- tests.isolated_vault_config( |
|
2678 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2675 | 2679 |
monkeypatch=monkeypatch, |
2676 | 2680 |
runner=runner, |
2677 | 2681 |
vault_config={ |
... | ... |
@@ -2686,9 +2690,9 @@ class TestCLI: |
2686 | 2690 |
) |
2687 | 2691 |
) |
2688 | 2692 |
monkeypatch.setattr( |
2689 |
- ssh_agent.SSHAgentClient, "list_keys", tests.list_keys |
|
2693 |
+ ssh_agent.SSHAgentClient, "list_keys", tests.data.callables.list_keys |
|
2690 | 2694 |
) |
2691 |
- monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.sign) |
|
2695 |
+ monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.data.callables.sign) |
|
2692 | 2696 |
result = runner.invoke( |
2693 | 2697 |
cli.derivepassphrase_vault, |
2694 | 2698 |
["--", DUMMY_SERVICE], |
... | ... |
@@ -2707,30 +2711,30 @@ class TestCLI: |
2707 | 2711 |
@Parametrize.KEY_OVERRIDING_IN_CONFIG |
2708 | 2712 |
def test_206_setting_phrase_thus_overriding_key_in_config( |
2709 | 2713 |
self, |
2710 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
2714 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
2711 | 2715 |
caplog: pytest.LogCaptureFixture, |
2712 | 2716 |
config: _types.VaultConfig, |
2713 | 2717 |
command_line: list[str], |
2714 | 2718 |
) -> None: |
2715 | 2719 |
"""Configuring a passphrase atop an SSH key works, but warns.""" |
2716 | 2720 |
del running_ssh_agent |
2717 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2721 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2718 | 2722 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2719 | 2723 |
# with-statements. |
2720 | 2724 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2721 | 2725 |
with contextlib.ExitStack() as stack: |
2722 | 2726 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2723 | 2727 |
stack.enter_context( |
2724 |
- tests.isolated_vault_config( |
|
2728 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2725 | 2729 |
monkeypatch=monkeypatch, |
2726 | 2730 |
runner=runner, |
2727 | 2731 |
vault_config=config, |
2728 | 2732 |
) |
2729 | 2733 |
) |
2730 | 2734 |
monkeypatch.setattr( |
2731 |
- ssh_agent.SSHAgentClient, "list_keys", tests.list_keys |
|
2735 |
+ ssh_agent.SSHAgentClient, "list_keys", tests.data.callables.list_keys |
|
2732 | 2736 |
) |
2733 |
- monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.sign) |
|
2737 |
+ monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", tests.data.callables.sign) |
|
2734 | 2738 |
result = runner.invoke( |
2735 | 2739 |
cli.derivepassphrase_vault, |
2736 | 2740 |
command_line, |
... | ... |
@@ -2742,10 +2746,10 @@ class TestCLI: |
2742 | 2746 |
assert result.stderr, "expected known error output" |
2743 | 2747 |
err_lines = result.stderr.splitlines(False) |
2744 | 2748 |
assert err_lines[0].startswith("Passphrase:") |
2745 |
- assert tests.warning_emitted( |
|
2749 |
+ assert tests.machinery.warning_emitted( |
|
2746 | 2750 |
"Setting a service passphrase is ineffective ", |
2747 | 2751 |
caplog.record_tuples, |
2748 |
- ) or tests.warning_emitted( |
|
2752 |
+ ) or tests.machinery.warning_emitted( |
|
2749 | 2753 |
"Setting a global passphrase is ineffective ", |
2750 | 2754 |
caplog.record_tuples, |
2751 | 2755 |
), "expected known warning message" |
... | ... |
@@ -2770,14 +2774,14 @@ class TestCLI: |
2770 | 2774 |
) -> None: |
2771 | 2775 |
"""Service notes are printed, if they exist.""" |
2772 | 2776 |
hypothesis.assume("Error:" not in notes) |
2773 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2777 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2774 | 2778 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2775 | 2779 |
# with-statements. |
2776 | 2780 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2777 | 2781 |
with contextlib.ExitStack() as stack: |
2778 | 2782 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2779 | 2783 |
stack.enter_context( |
2780 |
- tests.isolated_vault_config( |
|
2784 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2781 | 2785 |
monkeypatch=monkeypatch, |
2782 | 2786 |
runner=runner, |
2783 | 2787 |
vault_config={ |
... | ... |
@@ -2816,14 +2820,14 @@ class TestCLI: |
2816 | 2820 |
option: str, |
2817 | 2821 |
) -> None: |
2818 | 2822 |
"""Requesting invalidly many characters from a class fails.""" |
2819 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2823 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2820 | 2824 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2821 | 2825 |
# with-statements. |
2822 | 2826 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2823 | 2827 |
with contextlib.ExitStack() as stack: |
2824 | 2828 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2825 | 2829 |
stack.enter_context( |
2826 |
- tests.isolated_config( |
|
2830 |
+ tests.machinery.pytest.isolated_config( |
|
2827 | 2831 |
monkeypatch=monkeypatch, |
2828 | 2832 |
runner=runner, |
2829 | 2833 |
) |
... | ... |
@@ -2848,21 +2852,21 @@ class TestCLI: |
2848 | 2852 |
check_success: bool, |
2849 | 2853 |
) -> None: |
2850 | 2854 |
"""We require or forbid a service argument, depending on options.""" |
2851 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2855 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2852 | 2856 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2853 | 2857 |
# with-statements. |
2854 | 2858 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2855 | 2859 |
with contextlib.ExitStack() as stack: |
2856 | 2860 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2857 | 2861 |
stack.enter_context( |
2858 |
- tests.isolated_vault_config( |
|
2862 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2859 | 2863 |
monkeypatch=monkeypatch, |
2860 | 2864 |
runner=runner, |
2861 | 2865 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
2862 | 2866 |
) |
2863 | 2867 |
) |
2864 | 2868 |
monkeypatch.setattr( |
2865 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
2869 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
2866 | 2870 |
) |
2867 | 2871 |
result = runner.invoke( |
2868 | 2872 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2890,7 +2894,7 @@ class TestCLI: |
2890 | 2894 |
with contextlib.ExitStack() as stack: |
2891 | 2895 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2892 | 2896 |
stack.enter_context( |
2893 |
- tests.isolated_vault_config( |
|
2897 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2894 | 2898 |
monkeypatch=monkeypatch, |
2895 | 2899 |
runner=runner, |
2896 | 2900 |
vault_config={ |
... | ... |
@@ -2900,7 +2904,7 @@ class TestCLI: |
2900 | 2904 |
) |
2901 | 2905 |
) |
2902 | 2906 |
monkeypatch.setattr( |
2903 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
2907 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
2904 | 2908 |
) |
2905 | 2909 |
result = runner.invoke( |
2906 | 2910 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2923,25 +2927,25 @@ class TestCLI: |
2923 | 2927 |
def is_expected_warning(record: tuple[str, int, str]) -> bool: |
2924 | 2928 |
return is_harmless_config_import_warning( |
2925 | 2929 |
record |
2926 |
- ) or tests.warning_emitted( |
|
2930 |
+ ) or tests.machinery.warning_emitted( |
|
2927 | 2931 |
"An empty SERVICE is not supported by vault(1)", [record] |
2928 | 2932 |
) |
2929 | 2933 |
|
2930 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2934 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2931 | 2935 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2932 | 2936 |
# with-statements. |
2933 | 2937 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2934 | 2938 |
with contextlib.ExitStack() as stack: |
2935 | 2939 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2936 | 2940 |
stack.enter_context( |
2937 |
- tests.isolated_vault_config( |
|
2941 |
+ tests.machinery.pytest.isolated_vault_config( |
|
2938 | 2942 |
monkeypatch=monkeypatch, |
2939 | 2943 |
runner=runner, |
2940 | 2944 |
vault_config={"services": {}}, |
2941 | 2945 |
) |
2942 | 2946 |
) |
2943 | 2947 |
monkeypatch.setattr( |
2944 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
2948 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
2945 | 2949 |
) |
2946 | 2950 |
result = runner.invoke( |
2947 | 2951 |
cli.derivepassphrase_vault, |
... | ... |
@@ -2981,14 +2985,14 @@ class TestCLI: |
2981 | 2985 |
service: bool | None, |
2982 | 2986 |
) -> None: |
2983 | 2987 |
"""Incompatible options are detected.""" |
2984 |
- runner = tests.CliRunner(mix_stderr=False) |
|
2988 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
2985 | 2989 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2986 | 2990 |
# with-statements. |
2987 | 2991 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
2988 | 2992 |
with contextlib.ExitStack() as stack: |
2989 | 2993 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
2990 | 2994 |
stack.enter_context( |
2991 |
- tests.isolated_config( |
|
2995 |
+ tests.machinery.pytest.isolated_config( |
|
2992 | 2996 |
monkeypatch=monkeypatch, |
2993 | 2997 |
runner=runner, |
2994 | 2998 |
) |
... | ... |
@@ -3010,14 +3014,14 @@ class TestCLI: |
3010 | 3014 |
config: Any, |
3011 | 3015 |
) -> None: |
3012 | 3016 |
"""Importing a configuration works.""" |
3013 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3017 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3014 | 3018 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3015 | 3019 |
# with-statements. |
3016 | 3020 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3017 | 3021 |
with contextlib.ExitStack() as stack: |
3018 | 3022 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3019 | 3023 |
stack.enter_context( |
3020 |
- tests.isolated_vault_config( |
|
3024 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3021 | 3025 |
monkeypatch=monkeypatch, |
3022 | 3026 |
runner=runner, |
3023 | 3027 |
vault_config={"services": {}}, |
... | ... |
@@ -3047,18 +3051,18 @@ class TestCLI: |
3047 | 3051 |
], |
3048 | 3052 |
) |
3049 | 3053 |
@hypothesis.given( |
3050 |
- conf=tests.smudged_vault_test_config( |
|
3054 |
+ conf=tests.machinery.hypothesis.smudged_vault_test_config( |
|
3051 | 3055 |
strategies.sampled_from([ |
3052 | 3056 |
conf |
3053 |
- for conf in tests.TEST_CONFIGS |
|
3054 |
- if tests.is_valid_test_config(conf) |
|
3057 |
+ for conf in tests.data.TEST_CONFIGS |
|
3058 |
+ if tests.data.is_valid_test_config(conf) |
|
3055 | 3059 |
]) |
3056 | 3060 |
) |
3057 | 3061 |
) |
3058 | 3062 |
def test_213a_import_config_success( |
3059 | 3063 |
self, |
3060 | 3064 |
caplog: pytest.LogCaptureFixture, |
3061 |
- conf: tests.VaultTestConfig, |
|
3065 |
+ conf: tests.data.VaultTestConfig, |
|
3062 | 3066 |
) -> None: |
3063 | 3067 |
"""Importing a smudged configuration works. |
3064 | 3068 |
|
... | ... |
@@ -3070,14 +3074,14 @@ class TestCLI: |
3070 | 3074 |
_types.clean_up_falsy_vault_config_values(config2) |
3071 | 3075 |
# Reset caplog between hypothesis runs. |
3072 | 3076 |
caplog.clear() |
3073 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3077 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3074 | 3078 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3075 | 3079 |
# with-statements. |
3076 | 3080 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3077 | 3081 |
with contextlib.ExitStack() as stack: |
3078 | 3082 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3079 | 3083 |
stack.enter_context( |
3080 |
- tests.isolated_vault_config( |
|
3084 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3081 | 3085 |
monkeypatch=monkeypatch, |
3082 | 3086 |
runner=runner, |
3083 | 3087 |
vault_config={"services": {}}, |
... | ... |
@@ -3104,14 +3108,14 @@ class TestCLI: |
3104 | 3108 |
self, |
3105 | 3109 |
) -> None: |
3106 | 3110 |
"""Importing an invalid config fails.""" |
3107 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3111 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3108 | 3112 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3109 | 3113 |
# with-statements. |
3110 | 3114 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3111 | 3115 |
with contextlib.ExitStack() as stack: |
3112 | 3116 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3113 | 3117 |
stack.enter_context( |
3114 |
- tests.isolated_config( |
|
3118 |
+ tests.machinery.pytest.isolated_config( |
|
3115 | 3119 |
monkeypatch=monkeypatch, |
3116 | 3120 |
runner=runner, |
3117 | 3121 |
) |
... | ... |
@@ -3130,14 +3134,14 @@ class TestCLI: |
3130 | 3134 |
self, |
3131 | 3135 |
) -> None: |
3132 | 3136 |
"""Importing an invalid config fails.""" |
3133 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3137 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3134 | 3138 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3135 | 3139 |
# with-statements. |
3136 | 3140 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3137 | 3141 |
with contextlib.ExitStack() as stack: |
3138 | 3142 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3139 | 3143 |
stack.enter_context( |
3140 |
- tests.isolated_config( |
|
3144 |
+ tests.machinery.pytest.isolated_config( |
|
3141 | 3145 |
monkeypatch=monkeypatch, |
3142 | 3146 |
runner=runner, |
3143 | 3147 |
) |
... | ... |
@@ -3156,7 +3160,7 @@ class TestCLI: |
3156 | 3160 |
self, |
3157 | 3161 |
) -> None: |
3158 | 3162 |
"""Importing an invalid config fails.""" |
3159 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3163 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3160 | 3164 |
# `isolated_vault_config` ensures the configuration is valid |
3161 | 3165 |
# JSON. So, to pass an actual broken configuration, we must |
3162 | 3166 |
# open the configuration file ourselves afterwards, inside the |
... | ... |
@@ -3168,7 +3172,7 @@ class TestCLI: |
3168 | 3172 |
with contextlib.ExitStack() as stack: |
3169 | 3173 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3170 | 3174 |
stack.enter_context( |
3171 |
- tests.isolated_vault_config( |
|
3175 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3172 | 3176 |
monkeypatch=monkeypatch, |
3173 | 3177 |
runner=runner, |
3174 | 3178 |
vault_config={"services": {}}, |
... | ... |
@@ -3197,14 +3201,14 @@ class TestCLI: |
3197 | 3201 |
config: Any, |
3198 | 3202 |
) -> None: |
3199 | 3203 |
"""Exporting a configuration works.""" |
3200 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3204 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3201 | 3205 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3202 | 3206 |
# with-statements. |
3203 | 3207 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3204 | 3208 |
with contextlib.ExitStack() as stack: |
3205 | 3209 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3206 | 3210 |
stack.enter_context( |
3207 |
- tests.isolated_vault_config( |
|
3211 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3208 | 3212 |
monkeypatch=monkeypatch, |
3209 | 3213 |
runner=runner, |
3210 | 3214 |
vault_config=config, |
... | ... |
@@ -3237,14 +3241,14 @@ class TestCLI: |
3237 | 3241 |
export_options: list[str], |
3238 | 3242 |
) -> None: |
3239 | 3243 |
"""Exporting the default, empty config works.""" |
3240 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3244 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3241 | 3245 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3242 | 3246 |
# with-statements. |
3243 | 3247 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3244 | 3248 |
with contextlib.ExitStack() as stack: |
3245 | 3249 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3246 | 3250 |
stack.enter_context( |
3247 |
- tests.isolated_config( |
|
3251 |
+ tests.machinery.pytest.isolated_config( |
|
3248 | 3252 |
monkeypatch=monkeypatch, |
3249 | 3253 |
runner=runner, |
3250 | 3254 |
) |
... | ... |
@@ -3269,14 +3273,14 @@ class TestCLI: |
3269 | 3273 |
export_options: list[str], |
3270 | 3274 |
) -> None: |
3271 | 3275 |
"""Exporting an invalid config fails.""" |
3272 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3276 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3273 | 3277 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3274 | 3278 |
# with-statements. |
3275 | 3279 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3276 | 3280 |
with contextlib.ExitStack() as stack: |
3277 | 3281 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3278 | 3282 |
stack.enter_context( |
3279 |
- tests.isolated_vault_config( |
|
3283 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3280 | 3284 |
monkeypatch=monkeypatch, |
3281 | 3285 |
runner=runner, |
3282 | 3286 |
vault_config={}, |
... | ... |
@@ -3298,14 +3302,14 @@ class TestCLI: |
3298 | 3302 |
export_options: list[str], |
3299 | 3303 |
) -> None: |
3300 | 3304 |
"""Exporting an invalid config fails.""" |
3301 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3305 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3302 | 3306 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3303 | 3307 |
# with-statements. |
3304 | 3308 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3305 | 3309 |
with contextlib.ExitStack() as stack: |
3306 | 3310 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3307 | 3311 |
stack.enter_context( |
3308 |
- tests.isolated_config( |
|
3312 |
+ tests.machinery.pytest.isolated_config( |
|
3309 | 3313 |
monkeypatch=monkeypatch, |
3310 | 3314 |
runner=runner, |
3311 | 3315 |
) |
... | ... |
@@ -3329,14 +3333,14 @@ class TestCLI: |
3329 | 3333 |
export_options: list[str], |
3330 | 3334 |
) -> None: |
3331 | 3335 |
"""Exporting an invalid config fails.""" |
3332 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3336 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3333 | 3337 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3334 | 3338 |
# with-statements. |
3335 | 3339 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3336 | 3340 |
with contextlib.ExitStack() as stack: |
3337 | 3341 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3338 | 3342 |
stack.enter_context( |
3339 |
- tests.isolated_config( |
|
3343 |
+ tests.machinery.pytest.isolated_config( |
|
3340 | 3344 |
monkeypatch=monkeypatch, |
3341 | 3345 |
runner=runner, |
3342 | 3346 |
) |
... | ... |
@@ -3352,21 +3356,21 @@ class TestCLI: |
3352 | 3356 |
"expected error exit and known error message" |
3353 | 3357 |
) |
3354 | 3358 |
|
3355 |
- @tests.skip_if_on_the_annoying_os |
|
3359 |
+ @tests.machinery.pytest.skip_if_on_the_annoying_os |
|
3356 | 3360 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
3357 | 3361 |
def test_214e_export_settings_settings_directory_not_a_directory( |
3358 | 3362 |
self, |
3359 | 3363 |
export_options: list[str], |
3360 | 3364 |
) -> None: |
3361 | 3365 |
"""Exporting an invalid config fails.""" |
3362 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3366 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3363 | 3367 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3364 | 3368 |
# with-statements. |
3365 | 3369 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3366 | 3370 |
with contextlib.ExitStack() as stack: |
3367 | 3371 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3368 | 3372 |
stack.enter_context( |
3369 |
- tests.isolated_config( |
|
3373 |
+ tests.machinery.pytest.isolated_config( |
|
3370 | 3374 |
monkeypatch=monkeypatch, |
3371 | 3375 |
runner=runner, |
3372 | 3376 |
) |
... | ... |
@@ -3417,14 +3421,14 @@ class TestCLI: |
3417 | 3421 |
if notes_placement == "before" |
3418 | 3422 |
else f"{result_phrase}\n\n{notes}\n\n" |
3419 | 3423 |
) |
3420 |
- runner = tests.CliRunner(mix_stderr=True) |
|
3424 |
+ runner = tests.machinery.CliRunner(mix_stderr=True) |
|
3421 | 3425 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3422 | 3426 |
# with-statements. |
3423 | 3427 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3424 | 3428 |
with contextlib.ExitStack() as stack: |
3425 | 3429 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3426 | 3430 |
stack.enter_context( |
3427 |
- tests.isolated_vault_config( |
|
3431 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3428 | 3432 |
monkeypatch=monkeypatch, |
3429 | 3433 |
runner=runner, |
3430 | 3434 |
vault_config=vault_config, |
... | ... |
@@ -3470,14 +3474,14 @@ class TestCLI: |
3470 | 3474 |
""" |
3471 | 3475 |
# Reset caplog between hypothesis runs. |
3472 | 3476 |
caplog.clear() |
3473 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3477 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3474 | 3478 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3475 | 3479 |
# with-statements. |
3476 | 3480 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3477 | 3481 |
with contextlib.ExitStack() as stack: |
3478 | 3482 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3479 | 3483 |
stack.enter_context( |
3480 |
- tests.isolated_vault_config( |
|
3484 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3481 | 3485 |
monkeypatch=monkeypatch, |
3482 | 3486 |
runner=runner, |
3483 | 3487 |
vault_config={ |
... | ... |
@@ -3509,7 +3513,7 @@ class TestCLI: |
3509 | 3513 |
) |
3510 | 3514 |
assert result.clean_exit(), "expected clean exit" |
3511 | 3515 |
assert all(map(is_warning_line, result.stderr.splitlines(True))) |
3512 |
- assert modern_editor_interface or tests.warning_emitted( |
|
3516 |
+ assert modern_editor_interface or tests.machinery.warning_emitted( |
|
3513 | 3517 |
"A backup copy of the old notes was saved", |
3514 | 3518 |
caplog.record_tuples, |
3515 | 3519 |
), "expected known warning message in stderr" |
... | ... |
@@ -3560,14 +3564,14 @@ class TestCLI: |
3560 | 3564 |
return " " + notes.strip() + "\n\n\n\n\n\n" |
3561 | 3565 |
|
3562 | 3566 |
edit_funcs = {"empty": empty, "space": space} |
3563 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3567 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3564 | 3568 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3565 | 3569 |
# with-statements. |
3566 | 3570 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3567 | 3571 |
with contextlib.ExitStack() as stack: |
3568 | 3572 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3569 | 3573 |
stack.enter_context( |
3570 |
- tests.isolated_vault_config( |
|
3574 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3571 | 3575 |
monkeypatch=monkeypatch, |
3572 | 3576 |
runner=runner, |
3573 | 3577 |
vault_config={ |
... | ... |
@@ -3649,14 +3653,14 @@ class TestCLI: |
3649 | 3653 |
hypothesis.assume(str(notes_marker) not in notes.strip()) |
3650 | 3654 |
# Reset caplog between hypothesis runs. |
3651 | 3655 |
caplog.clear() |
3652 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3656 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3653 | 3657 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3654 | 3658 |
# with-statements. |
3655 | 3659 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3656 | 3660 |
with contextlib.ExitStack() as stack: |
3657 | 3661 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3658 | 3662 |
stack.enter_context( |
3659 |
- tests.isolated_vault_config( |
|
3663 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3660 | 3664 |
monkeypatch=monkeypatch, |
3661 | 3665 |
runner=runner, |
3662 | 3666 |
vault_config={ |
... | ... |
@@ -3690,7 +3694,7 @@ class TestCLI: |
3690 | 3694 |
assert not result.stderr or all( |
3691 | 3695 |
map(is_warning_line, result.stderr.splitlines(True)) |
3692 | 3696 |
) |
3693 |
- assert not caplog.record_tuples or tests.warning_emitted( |
|
3697 |
+ assert not caplog.record_tuples or tests.machinery.warning_emitted( |
|
3694 | 3698 |
"A backup copy of the old notes was saved", |
3695 | 3699 |
caplog.record_tuples, |
3696 | 3700 |
), "expected known warning message in stderr" |
... | ... |
@@ -3726,14 +3730,14 @@ class TestCLI: |
3726 | 3730 |
Aborting is only supported with the modern editor interface. |
3727 | 3731 |
|
3728 | 3732 |
""" |
3729 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3733 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3730 | 3734 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3731 | 3735 |
# with-statements. |
3732 | 3736 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3733 | 3737 |
with contextlib.ExitStack() as stack: |
3734 | 3738 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3735 | 3739 |
stack.enter_context( |
3736 |
- tests.isolated_vault_config( |
|
3740 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3737 | 3741 |
monkeypatch=monkeypatch, |
3738 | 3742 |
runner=runner, |
3739 | 3743 |
vault_config={ |
... | ... |
@@ -3774,14 +3778,14 @@ class TestCLI: |
3774 | 3778 |
Aborting is only supported with the modern editor interface. |
3775 | 3779 |
|
3776 | 3780 |
""" |
3777 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3781 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3778 | 3782 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3779 | 3783 |
# with-statements. |
3780 | 3784 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3781 | 3785 |
with contextlib.ExitStack() as stack: |
3782 | 3786 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3783 | 3787 |
stack.enter_context( |
3784 |
- tests.isolated_vault_config( |
|
3788 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3785 | 3789 |
monkeypatch=monkeypatch, |
3786 | 3790 |
runner=runner, |
3787 | 3791 |
vault_config={ |
... | ... |
@@ -3845,14 +3849,14 @@ class TestCLI: |
3845 | 3849 |
} |
3846 | 3850 |
# Reset caplog between hypothesis runs. |
3847 | 3851 |
caplog.clear() |
3848 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3852 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3849 | 3853 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3850 | 3854 |
# with-statements. |
3851 | 3855 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3852 | 3856 |
with contextlib.ExitStack() as stack: |
3853 | 3857 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3854 | 3858 |
stack.enter_context( |
3855 |
- tests.isolated_vault_config( |
|
3859 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3856 | 3860 |
monkeypatch=monkeypatch, |
3857 | 3861 |
runner=runner, |
3858 | 3862 |
vault_config=vault_config, |
... | ... |
@@ -3893,7 +3897,7 @@ class TestCLI: |
3893 | 3897 |
for line in result.stderr.splitlines(True) |
3894 | 3898 |
if line.startswith(f"{cli.PROG_NAME}: ") |
3895 | 3899 |
) |
3896 |
- assert tests.warning_emitted( |
|
3900 |
+ assert tests.machinery.warning_emitted( |
|
3897 | 3901 |
"Specifying --notes without --config is ineffective. " |
3898 | 3902 |
"No notes will be edited.", |
3899 | 3903 |
caplog.record_tuples, |
... | ... |
@@ -3922,21 +3926,21 @@ class TestCLI: |
3922 | 3926 |
the config more readable. |
3923 | 3927 |
|
3924 | 3928 |
""" |
3925 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3929 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3926 | 3930 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3927 | 3931 |
# with-statements. |
3928 | 3932 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3929 | 3933 |
with contextlib.ExitStack() as stack: |
3930 | 3934 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3931 | 3935 |
stack.enter_context( |
3932 |
- tests.isolated_vault_config( |
|
3936 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3933 | 3937 |
monkeypatch=monkeypatch, |
3934 | 3938 |
runner=runner, |
3935 | 3939 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
3936 | 3940 |
) |
3937 | 3941 |
) |
3938 | 3942 |
monkeypatch.setattr( |
3939 |
- cli_helpers, "get_suitable_ssh_keys", tests.suitable_ssh_keys |
|
3943 |
+ cli_helpers, "get_suitable_ssh_keys", tests.data.callables.suitable_ssh_keys |
|
3940 | 3944 |
) |
3941 | 3945 |
result = runner.invoke( |
3942 | 3946 |
cli.derivepassphrase_vault, |
... | ... |
@@ -3962,21 +3966,21 @@ class TestCLI: |
3962 | 3966 |
err_text: str, |
3963 | 3967 |
) -> None: |
3964 | 3968 |
"""Storing invalid settings via `--config` fails.""" |
3965 |
- runner = tests.CliRunner(mix_stderr=False) |
|
3969 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3966 | 3970 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3967 | 3971 |
# with-statements. |
3968 | 3972 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
3969 | 3973 |
with contextlib.ExitStack() as stack: |
3970 | 3974 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
3971 | 3975 |
stack.enter_context( |
3972 |
- tests.isolated_vault_config( |
|
3976 |
+ tests.machinery.pytest.isolated_vault_config( |
|
3973 | 3977 |
monkeypatch=monkeypatch, |
3974 | 3978 |
runner=runner, |
3975 | 3979 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
3976 | 3980 |
) |
3977 | 3981 |
) |
3978 | 3982 |
monkeypatch.setattr( |
3979 |
- cli_helpers, "get_suitable_ssh_keys", tests.suitable_ssh_keys |
|
3983 |
+ cli_helpers, "get_suitable_ssh_keys", tests.data.callables.suitable_ssh_keys |
|
3980 | 3984 |
) |
3981 | 3985 |
result = runner.invoke( |
3982 | 3986 |
cli.derivepassphrase_vault, |
... | ... |
@@ -3990,18 +3994,18 @@ class TestCLI: |
3990 | 3994 |
|
3991 | 3995 |
def test_225a_store_config_fail_manual_no_ssh_key_selection( |
3992 | 3996 |
self, |
3993 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
3997 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
3994 | 3998 |
) -> None: |
3995 | 3999 |
"""Not selecting an SSH key during `--config --key` fails.""" |
3996 | 4000 |
del running_ssh_agent |
3997 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4001 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
3998 | 4002 |
# TODO(the-13th-letter): Rewrite using parenthesized |
3999 | 4003 |
# with-statements. |
4000 | 4004 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4001 | 4005 |
with contextlib.ExitStack() as stack: |
4002 | 4006 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4003 | 4007 |
stack.enter_context( |
4004 |
- tests.isolated_vault_config( |
|
4008 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4005 | 4009 |
monkeypatch=monkeypatch, |
4006 | 4010 |
runner=runner, |
4007 | 4011 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4017,7 +4021,7 @@ class TestCLI: |
4017 | 4021 |
# Also patch the list of suitable SSH keys, lest we be at |
4018 | 4022 |
# the mercy of whatever SSH agent may be running. |
4019 | 4023 |
monkeypatch.setattr( |
4020 |
- cli_helpers, "get_suitable_ssh_keys", tests.suitable_ssh_keys |
|
4024 |
+ cli_helpers, "get_suitable_ssh_keys", tests.data.callables.suitable_ssh_keys |
|
4021 | 4025 |
) |
4022 | 4026 |
result = runner.invoke( |
4023 | 4027 |
cli.derivepassphrase_vault, |
... | ... |
@@ -4030,18 +4034,18 @@ class TestCLI: |
4030 | 4034 |
|
4031 | 4035 |
def test_225b_store_config_fail_manual_no_ssh_agent( |
4032 | 4036 |
self, |
4033 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
4037 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
4034 | 4038 |
) -> None: |
4035 | 4039 |
"""Not running an SSH agent during `--config --key` fails.""" |
4036 | 4040 |
del running_ssh_agent |
4037 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4041 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4038 | 4042 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4039 | 4043 |
# with-statements. |
4040 | 4044 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4041 | 4045 |
with contextlib.ExitStack() as stack: |
4042 | 4046 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4043 | 4047 |
stack.enter_context( |
4044 |
- tests.isolated_vault_config( |
|
4048 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4045 | 4049 |
monkeypatch=monkeypatch, |
4046 | 4050 |
runner=runner, |
4047 | 4051 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4059,18 +4063,18 @@ class TestCLI: |
4059 | 4063 |
|
4060 | 4064 |
def test_225c_store_config_fail_manual_bad_ssh_agent_connection( |
4061 | 4065 |
self, |
4062 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
4066 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
4063 | 4067 |
) -> None: |
4064 | 4068 |
"""Not running a reachable SSH agent during `--config --key` fails.""" |
4065 | 4069 |
running_ssh_agent.require_external_address() |
4066 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4070 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4067 | 4071 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4068 | 4072 |
# with-statements. |
4069 | 4073 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4070 | 4074 |
with contextlib.ExitStack() as stack: |
4071 | 4075 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4072 | 4076 |
stack.enter_context( |
4073 |
- tests.isolated_vault_config( |
|
4077 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4074 | 4078 |
monkeypatch=monkeypatch, |
4075 | 4079 |
runner=runner, |
4076 | 4080 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4093,20 +4097,20 @@ class TestCLI: |
4093 | 4097 |
try_race_free_implementation: bool, |
4094 | 4098 |
) -> None: |
4095 | 4099 |
"""Using a read-only configuration file with `--config` fails.""" |
4096 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4100 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4097 | 4101 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4098 | 4102 |
# with-statements. |
4099 | 4103 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4100 | 4104 |
with contextlib.ExitStack() as stack: |
4101 | 4105 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4102 | 4106 |
stack.enter_context( |
4103 |
- tests.isolated_vault_config( |
|
4107 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4104 | 4108 |
monkeypatch=monkeypatch, |
4105 | 4109 |
runner=runner, |
4106 | 4110 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
4107 | 4111 |
) |
4108 | 4112 |
) |
4109 |
- tests.make_file_readonly( |
|
4113 |
+ tests.data.callables.make_file_readonly( |
|
4110 | 4114 |
cli_helpers.config_filename(subsystem="vault"), |
4111 | 4115 |
try_race_free_implementation=try_race_free_implementation, |
4112 | 4116 |
) |
... | ... |
@@ -4123,14 +4127,14 @@ class TestCLI: |
4123 | 4127 |
self, |
4124 | 4128 |
) -> None: |
4125 | 4129 |
"""OS-erroring with `--config` fails.""" |
4126 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4130 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4127 | 4131 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4128 | 4132 |
# with-statements. |
4129 | 4133 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4130 | 4134 |
with contextlib.ExitStack() as stack: |
4131 | 4135 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4132 | 4136 |
stack.enter_context( |
4133 |
- tests.isolated_vault_config( |
|
4137 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4134 | 4138 |
monkeypatch=monkeypatch, |
4135 | 4139 |
runner=runner, |
4136 | 4140 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4156,14 +4160,14 @@ class TestCLI: |
4156 | 4160 |
self, |
4157 | 4161 |
) -> None: |
4158 | 4162 |
"""Issuing conflicting settings to `--config` fails.""" |
4159 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4163 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4160 | 4164 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4161 | 4165 |
# with-statements. |
4162 | 4166 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4163 | 4167 |
with contextlib.ExitStack() as stack: |
4164 | 4168 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4165 | 4169 |
stack.enter_context( |
4166 |
- tests.isolated_vault_config( |
|
4170 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4167 | 4171 |
monkeypatch=monkeypatch, |
4168 | 4172 |
runner=runner, |
4169 | 4173 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4186,18 +4190,18 @@ class TestCLI: |
4186 | 4190 |
|
4187 | 4191 |
def test_225g_store_config_fail_manual_ssh_agent_no_keys_loaded( |
4188 | 4192 |
self, |
4189 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
4193 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
4190 | 4194 |
) -> None: |
4191 | 4195 |
"""Not holding any SSH keys during `--config --key` fails.""" |
4192 | 4196 |
del running_ssh_agent |
4193 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4197 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4194 | 4198 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4195 | 4199 |
# with-statements. |
4196 | 4200 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4197 | 4201 |
with contextlib.ExitStack() as stack: |
4198 | 4202 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4199 | 4203 |
stack.enter_context( |
4200 |
- tests.isolated_vault_config( |
|
4204 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4201 | 4205 |
monkeypatch=monkeypatch, |
4202 | 4206 |
runner=runner, |
4203 | 4207 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4222,18 +4226,18 @@ class TestCLI: |
4222 | 4226 |
|
4223 | 4227 |
def test_225h_store_config_fail_manual_ssh_agent_runtime_error( |
4224 | 4228 |
self, |
4225 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
4229 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
4226 | 4230 |
) -> None: |
4227 | 4231 |
"""The SSH agent erroring during `--config --key` fails.""" |
4228 | 4232 |
del running_ssh_agent |
4229 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4233 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4230 | 4234 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4231 | 4235 |
# with-statements. |
4232 | 4236 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4233 | 4237 |
with contextlib.ExitStack() as stack: |
4234 | 4238 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4235 | 4239 |
stack.enter_context( |
4236 |
- tests.isolated_vault_config( |
|
4240 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4237 | 4241 |
monkeypatch=monkeypatch, |
4238 | 4242 |
runner=runner, |
4239 | 4243 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4255,18 +4259,18 @@ class TestCLI: |
4255 | 4259 |
|
4256 | 4260 |
def test_225i_store_config_fail_manual_ssh_agent_refuses( |
4257 | 4261 |
self, |
4258 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
4262 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
4259 | 4263 |
) -> None: |
4260 | 4264 |
"""The SSH agent refusing during `--config --key` fails.""" |
4261 | 4265 |
del running_ssh_agent |
4262 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4266 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4263 | 4267 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4264 | 4268 |
# with-statements. |
4265 | 4269 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4266 | 4270 |
with contextlib.ExitStack() as stack: |
4267 | 4271 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4268 | 4272 |
stack.enter_context( |
4269 |
- tests.isolated_vault_config( |
|
4273 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4270 | 4274 |
monkeypatch=monkeypatch, |
4271 | 4275 |
runner=runner, |
4272 | 4276 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4290,14 +4294,14 @@ class TestCLI: |
4290 | 4294 |
|
4291 | 4295 |
def test_226_no_arguments(self) -> None: |
4292 | 4296 |
"""Calling `derivepassphrase vault` without any arguments fails.""" |
4293 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4297 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4294 | 4298 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4295 | 4299 |
# with-statements. |
4296 | 4300 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4297 | 4301 |
with contextlib.ExitStack() as stack: |
4298 | 4302 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4299 | 4303 |
stack.enter_context( |
4300 |
- tests.isolated_config( |
|
4304 |
+ tests.machinery.pytest.isolated_config( |
|
4301 | 4305 |
monkeypatch=monkeypatch, |
4302 | 4306 |
runner=runner, |
4303 | 4307 |
) |
... | ... |
@@ -4313,14 +4317,14 @@ class TestCLI: |
4313 | 4317 |
self, |
4314 | 4318 |
) -> None: |
4315 | 4319 |
"""Deriving a passphrase without a passphrase or key fails.""" |
4316 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4320 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4317 | 4321 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4318 | 4322 |
# with-statements. |
4319 | 4323 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4320 | 4324 |
with contextlib.ExitStack() as stack: |
4321 | 4325 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4322 | 4326 |
stack.enter_context( |
4323 |
- tests.isolated_config( |
|
4327 |
+ tests.machinery.pytest.isolated_config( |
|
4324 | 4328 |
monkeypatch=monkeypatch, |
4325 | 4329 |
runner=runner, |
4326 | 4330 |
) |
... | ... |
@@ -4344,14 +4348,14 @@ class TestCLI: |
4344 | 4348 |
[issue #6]: https://github.com/the-13th-letter/derivepassphrase/issues/6 |
4345 | 4349 |
|
4346 | 4350 |
""" |
4347 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4351 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4348 | 4352 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4349 | 4353 |
# with-statements. |
4350 | 4354 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4351 | 4355 |
with contextlib.ExitStack() as stack: |
4352 | 4356 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4353 | 4357 |
stack.enter_context( |
4354 |
- tests.isolated_config( |
|
4358 |
+ tests.machinery.pytest.isolated_config( |
|
4355 | 4359 |
monkeypatch=monkeypatch, |
4356 | 4360 |
runner=runner, |
4357 | 4361 |
) |
... | ... |
@@ -4390,14 +4394,14 @@ class TestCLI: |
4390 | 4394 |
[issue #6]: https://github.com/the-13th-letter/derivepassphrase/issues/6 |
4391 | 4395 |
|
4392 | 4396 |
""" |
4393 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4397 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4394 | 4398 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4395 | 4399 |
# with-statements. |
4396 | 4400 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4397 | 4401 |
with contextlib.ExitStack() as stack: |
4398 | 4402 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4399 | 4403 |
stack.enter_context( |
4400 |
- tests.isolated_config( |
|
4404 |
+ tests.machinery.pytest.isolated_config( |
|
4401 | 4405 |
monkeypatch=monkeypatch, |
4402 | 4406 |
runner=runner, |
4403 | 4407 |
) |
... | ... |
@@ -4429,14 +4433,14 @@ class TestCLI: |
4429 | 4433 |
self, |
4430 | 4434 |
) -> None: |
4431 | 4435 |
"""Storing the configuration reacts even to weird errors.""" |
4432 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4436 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4433 | 4437 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4434 | 4438 |
# with-statements. |
4435 | 4439 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4436 | 4440 |
with contextlib.ExitStack() as stack: |
4437 | 4441 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4438 | 4442 |
stack.enter_context( |
4439 |
- tests.isolated_config( |
|
4443 |
+ tests.machinery.pytest.isolated_config( |
|
4440 | 4444 |
monkeypatch=monkeypatch, |
4441 | 4445 |
runner=runner, |
4442 | 4446 |
) |
... | ... |
@@ -4468,14 +4472,14 @@ class TestCLI: |
4468 | 4472 |
warning_message: str, |
4469 | 4473 |
) -> None: |
4470 | 4474 |
"""Using unnormalized Unicode passphrases warns.""" |
4471 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4475 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4472 | 4476 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4473 | 4477 |
# with-statements. |
4474 | 4478 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4475 | 4479 |
with contextlib.ExitStack() as stack: |
4476 | 4480 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4477 | 4481 |
stack.enter_context( |
4478 |
- tests.isolated_vault_config( |
|
4482 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4479 | 4483 |
monkeypatch=monkeypatch, |
4480 | 4484 |
runner=runner, |
4481 | 4485 |
vault_config={ |
... | ... |
@@ -4493,7 +4497,7 @@ class TestCLI: |
4493 | 4497 |
input=input, |
4494 | 4498 |
) |
4495 | 4499 |
assert result.clean_exit(), "expected clean exit" |
4496 |
- assert tests.warning_emitted(warning_message, caplog.record_tuples), ( |
|
4500 |
+ assert tests.machinery.warning_emitted(warning_message, caplog.record_tuples), ( |
|
4497 | 4501 |
"expected known warning message in stderr" |
4498 | 4502 |
) |
4499 | 4503 |
|
... | ... |
@@ -4506,14 +4510,14 @@ class TestCLI: |
4506 | 4510 |
error_message: str, |
4507 | 4511 |
) -> None: |
4508 | 4512 |
"""Using unknown Unicode normalization forms fails.""" |
4509 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4513 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4510 | 4514 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4511 | 4515 |
# with-statements. |
4512 | 4516 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4513 | 4517 |
with contextlib.ExitStack() as stack: |
4514 | 4518 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4515 | 4519 |
stack.enter_context( |
4516 |
- tests.isolated_vault_config( |
|
4520 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4517 | 4521 |
monkeypatch=monkeypatch, |
4518 | 4522 |
runner=runner, |
4519 | 4523 |
vault_config={ |
... | ... |
@@ -4543,14 +4547,14 @@ class TestCLI: |
4543 | 4547 |
command_line: list[str], |
4544 | 4548 |
) -> None: |
4545 | 4549 |
"""Using unknown Unicode normalization forms in the config fails.""" |
4546 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4550 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4547 | 4551 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4548 | 4552 |
# with-statements. |
4549 | 4553 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4550 | 4554 |
with contextlib.ExitStack() as stack: |
4551 | 4555 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4552 | 4556 |
stack.enter_context( |
4553 |
- tests.isolated_vault_config( |
|
4557 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4554 | 4558 |
monkeypatch=monkeypatch, |
4555 | 4559 |
runner=runner, |
4556 | 4560 |
vault_config={ |
... | ... |
@@ -4583,14 +4587,14 @@ class TestCLI: |
4583 | 4587 |
self, |
4584 | 4588 |
) -> None: |
4585 | 4589 |
"""Loading a user configuration file in an invalid format fails.""" |
4586 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4590 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4587 | 4591 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4588 | 4592 |
# with-statements. |
4589 | 4593 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4590 | 4594 |
with contextlib.ExitStack() as stack: |
4591 | 4595 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4592 | 4596 |
stack.enter_context( |
4593 |
- tests.isolated_vault_config( |
|
4597 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4594 | 4598 |
monkeypatch=monkeypatch, |
4595 | 4599 |
runner=runner, |
4596 | 4600 |
vault_config={"services": {}}, |
... | ... |
@@ -4611,14 +4615,14 @@ class TestCLI: |
4611 | 4615 |
self, |
4612 | 4616 |
) -> None: |
4613 | 4617 |
"""Loading a user configuration file in an invalid format fails.""" |
4614 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4618 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4615 | 4619 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4616 | 4620 |
# with-statements. |
4617 | 4621 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4618 | 4622 |
with contextlib.ExitStack() as stack: |
4619 | 4623 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4620 | 4624 |
stack.enter_context( |
4621 |
- tests.isolated_vault_config( |
|
4625 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4622 | 4626 |
monkeypatch=monkeypatch, |
4623 | 4627 |
runner=runner, |
4624 | 4628 |
vault_config={"services": {}}, |
... | ... |
@@ -4645,14 +4649,14 @@ class TestCLI: |
4645 | 4649 |
caplog: pytest.LogCaptureFixture, |
4646 | 4650 |
) -> None: |
4647 | 4651 |
"""Querying the SSH agent without `AF_UNIX` support fails.""" |
4648 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4652 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4649 | 4653 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4650 | 4654 |
# with-statements. |
4651 | 4655 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4652 | 4656 |
with contextlib.ExitStack() as stack: |
4653 | 4657 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4654 | 4658 |
stack.enter_context( |
4655 |
- tests.isolated_vault_config( |
|
4659 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4656 | 4660 |
monkeypatch=monkeypatch, |
4657 | 4661 |
runner=runner, |
4658 | 4662 |
vault_config={"global": {"phrase": "abc"}, "services": {}}, |
... | ... |
@@ -4673,7 +4677,7 @@ class TestCLI: |
4673 | 4677 |
assert result.error_exit( |
4674 | 4678 |
error="does not support communicating with it" |
4675 | 4679 |
), "expected error exit and known error message" |
4676 |
- assert tests.warning_emitted( |
|
4680 |
+ assert tests.machinery.warning_emitted( |
|
4677 | 4681 |
"Cannot connect to an SSH agent via UNIX domain sockets", |
4678 | 4682 |
caplog.record_tuples, |
4679 | 4683 |
), "expected known warning message in stderr" |
... | ... |
@@ -4688,14 +4692,14 @@ class TestCLIUtils: |
4688 | 4692 |
config: Any, |
4689 | 4693 |
) -> None: |
4690 | 4694 |
"""[`cli_helpers.load_config`][] works for valid configurations.""" |
4691 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4695 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4692 | 4696 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4693 | 4697 |
# with-statements. |
4694 | 4698 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4695 | 4699 |
with contextlib.ExitStack() as stack: |
4696 | 4700 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4697 | 4701 |
stack.enter_context( |
4698 |
- tests.isolated_vault_config( |
|
4702 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4699 | 4703 |
monkeypatch=monkeypatch, |
4700 | 4704 |
runner=runner, |
4701 | 4705 |
vault_config=config, |
... | ... |
@@ -4710,14 +4714,14 @@ class TestCLIUtils: |
4710 | 4714 |
self, |
4711 | 4715 |
) -> None: |
4712 | 4716 |
"""[`cli_helpers.save_config`][] fails for bad configurations.""" |
4713 |
- runner = tests.CliRunner(mix_stderr=False) |
|
4717 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
4714 | 4718 |
# TODO(the-13th-letter): Rewrite using parenthesized |
4715 | 4719 |
# with-statements. |
4716 | 4720 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
4717 | 4721 |
with contextlib.ExitStack() as stack: |
4718 | 4722 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
4719 | 4723 |
stack.enter_context( |
4720 |
- tests.isolated_vault_config( |
|
4724 |
+ tests.machinery.pytest.isolated_vault_config( |
|
4721 | 4725 |
monkeypatch=monkeypatch, |
4722 | 4726 |
runner=runner, |
4723 | 4727 |
vault_config={}, |
... | ... |
@@ -4760,7 +4764,7 @@ class TestCLIUtils: |
4760 | 4764 |
click.echo(items[index]) |
4761 | 4765 |
click.echo("(Note: Vikings strictly optional.)") |
4762 | 4766 |
|
4763 |
- runner = tests.CliRunner(mix_stderr=True) |
|
4767 |
+ runner = tests.machinery.CliRunner(mix_stderr=True) |
|
4764 | 4768 |
result = runner.invoke(driver, [], input="9") |
4765 | 4769 |
assert result.clean_exit( |
4766 | 4770 |
output="""\ |
... | ... |
@@ -4857,7 +4861,7 @@ Your selection? (1-10, leave empty to abort): """, |
4857 | 4861 |
else: |
4858 | 4862 |
click.echo("Great!") |
4859 | 4863 |
|
4860 |
- runner = tests.CliRunner(mix_stderr=True) |
|
4864 |
+ runner = tests.machinery.CliRunner(mix_stderr=True) |
|
4861 | 4865 |
result = runner.invoke( |
4862 | 4866 |
driver, ["Will replace with spam. Confirm, y/n?"], input="y" |
4863 | 4867 |
) |
... | ... |
@@ -5072,14 +5076,14 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5072 | 5076 |
config, outfile=outfile, prog_name_list=prog_name_list |
5073 | 5077 |
) |
5074 | 5078 |
script = outfile.getvalue() |
5075 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5079 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5076 | 5080 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5077 | 5081 |
# with-statements. |
5078 | 5082 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5079 | 5083 |
with contextlib.ExitStack() as stack: |
5080 | 5084 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5081 | 5085 |
stack.enter_context( |
5082 |
- tests.isolated_vault_config( |
|
5086 |
+ tests.machinery.pytest.isolated_vault_config( |
|
5083 | 5087 |
monkeypatch=monkeypatch, |
5084 | 5088 |
runner=runner, |
5085 | 5089 |
vault_config={"services": {}}, |
... | ... |
@@ -5090,7 +5094,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5090 | 5094 |
assert cli_helpers.load_config() == config |
5091 | 5095 |
|
5092 | 5096 |
@hypothesis.given( |
5093 |
- global_config_settable=tests.vault_full_service_config(), |
|
5097 |
+ global_config_settable=tests.machinery.hypothesis.vault_full_service_config(), |
|
5094 | 5098 |
global_config_importable=strategies.fixed_dictionaries( |
5095 | 5099 |
{}, |
5096 | 5100 |
optional={ |
... | ... |
@@ -5185,7 +5189,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5185 | 5189 |
min_size=4, |
5186 | 5190 |
max_size=64, |
5187 | 5191 |
), |
5188 |
- service_config_settable=tests.vault_full_service_config(), |
|
5192 |
+ service_config_settable=tests.machinery.hypothesis.vault_full_service_config(), |
|
5189 | 5193 |
service_config_importable=strategies.fixed_dictionaries( |
5190 | 5194 |
{}, |
5191 | 5195 |
optional={ |
... | ... |
@@ -5342,14 +5346,14 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5342 | 5346 |
finally: |
5343 | 5347 |
shutil.rmtree(path) |
5344 | 5348 |
|
5345 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5349 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5346 | 5350 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5347 | 5351 |
# with-statements. |
5348 | 5352 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5349 | 5353 |
with contextlib.ExitStack() as stack: |
5350 | 5354 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5351 | 5355 |
stack.enter_context( |
5352 |
- tests.isolated_vault_config( |
|
5356 |
+ tests.machinery.pytest.isolated_vault_config( |
|
5353 | 5357 |
monkeypatch=monkeypatch, |
5354 | 5358 |
runner=runner, |
5355 | 5359 |
vault_config={"services": {}}, |
... | ... |
@@ -5372,7 +5376,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5372 | 5376 |
system_tempdir = os.fsdecode(tempfile.gettempdir()) |
5373 | 5377 |
our_tempdir = cli_helpers.get_tempdir() |
5374 | 5378 |
assert system_tempdir == os.fsdecode(our_tempdir) or ( |
5375 |
- # TODO(the-13th-letter): `tests.isolated_config` |
|
5379 |
+ # TODO(the-13th-letter): `tests.machinery.pytest.isolated_config` |
|
5376 | 5380 |
# guarantees that `Path.cwd() == config_filename(None)`. |
5377 | 5381 |
# So this sub-branch ought to never trigger in our |
5378 | 5382 |
# tests. |
... | ... |
@@ -5389,14 +5393,14 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5389 | 5393 |
configuration directory. |
5390 | 5394 |
|
5391 | 5395 |
""" |
5392 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5396 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5393 | 5397 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5394 | 5398 |
# with-statements. |
5395 | 5399 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5396 | 5400 |
with contextlib.ExitStack() as stack: |
5397 | 5401 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5398 | 5402 |
stack.enter_context( |
5399 |
- tests.isolated_vault_config( |
|
5403 |
+ tests.machinery.pytest.isolated_vault_config( |
|
5400 | 5404 |
monkeypatch=monkeypatch, |
5401 | 5405 |
runner=runner, |
5402 | 5406 |
vault_config={"services": {}}, |
... | ... |
@@ -5444,14 +5448,14 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5444 | 5448 |
) -> None: |
5445 | 5449 |
"""Repeatedly removing the same parts of a configuration works.""" |
5446 | 5450 |
for start_config in [config, result_config]: |
5447 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5451 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5448 | 5452 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5449 | 5453 |
# with-statements. |
5450 | 5454 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5451 | 5455 |
with contextlib.ExitStack() as stack: |
5452 | 5456 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5453 | 5457 |
stack.enter_context( |
5454 |
- tests.isolated_vault_config( |
|
5458 |
+ tests.machinery.pytest.isolated_vault_config( |
|
5455 | 5459 |
monkeypatch=monkeypatch, |
5456 | 5460 |
runner=runner, |
5457 | 5461 |
vault_config=start_config, |
... | ... |
@@ -5494,13 +5498,13 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo. |
5494 | 5498 |
@Parametrize.CONNECTION_HINTS |
5495 | 5499 |
def test_227_get_suitable_ssh_keys( |
5496 | 5500 |
self, |
5497 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
5501 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
5498 | 5502 |
conn_hint: str, |
5499 | 5503 |
) -> None: |
5500 | 5504 |
"""[`cli_helpers.get_suitable_ssh_keys`][] works.""" |
5501 | 5505 |
with pytest.MonkeyPatch.context() as monkeypatch: |
5502 | 5506 |
monkeypatch.setattr( |
5503 |
- ssh_agent.SSHAgentClient, "list_keys", tests.list_keys |
|
5507 |
+ ssh_agent.SSHAgentClient, "list_keys", tests.data.callables.list_keys |
|
5504 | 5508 |
) |
5505 | 5509 |
hint: ssh_agent.SSHAgentClient | _types.SSHAgentSocket | None |
5506 | 5510 |
# TODO(the-13th-letter): Rewrite using structural pattern |
... | ... |
@@ -5595,14 +5599,14 @@ class TestCLITransition: |
5595 | 5599 |
config: Any, |
5596 | 5600 |
) -> None: |
5597 | 5601 |
"""Loading the old settings file works.""" |
5598 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5602 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5599 | 5603 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5600 | 5604 |
# with-statements. |
5601 | 5605 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5602 | 5606 |
with contextlib.ExitStack() as stack: |
5603 | 5607 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5604 | 5608 |
stack.enter_context( |
5605 |
- tests.isolated_config( |
|
5609 |
+ tests.machinery.pytest.isolated_config( |
|
5606 | 5610 |
monkeypatch=monkeypatch, |
5607 | 5611 |
runner=runner, |
5608 | 5612 |
) |
... | ... |
@@ -5618,14 +5622,14 @@ class TestCLITransition: |
5618 | 5622 |
config: Any, |
5619 | 5623 |
) -> None: |
5620 | 5624 |
"""Migrating the old settings file works.""" |
5621 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5625 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5622 | 5626 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5623 | 5627 |
# with-statements. |
5624 | 5628 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5625 | 5629 |
with contextlib.ExitStack() as stack: |
5626 | 5630 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5627 | 5631 |
stack.enter_context( |
5628 |
- tests.isolated_config( |
|
5632 |
+ tests.machinery.pytest.isolated_config( |
|
5629 | 5633 |
monkeypatch=monkeypatch, |
5630 | 5634 |
runner=runner, |
5631 | 5635 |
) |
... | ... |
@@ -5641,14 +5645,14 @@ class TestCLITransition: |
5641 | 5645 |
config: Any, |
5642 | 5646 |
) -> None: |
5643 | 5647 |
"""Migrating the old settings file atop a directory fails.""" |
5644 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5648 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5645 | 5649 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5646 | 5650 |
# with-statements. |
5647 | 5651 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5648 | 5652 |
with contextlib.ExitStack() as stack: |
5649 | 5653 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5650 | 5654 |
stack.enter_context( |
5651 |
- tests.isolated_config( |
|
5655 |
+ tests.machinery.pytest.isolated_config( |
|
5652 | 5656 |
monkeypatch=monkeypatch, |
5653 | 5657 |
runner=runner, |
5654 | 5658 |
) |
... | ... |
@@ -5671,14 +5675,14 @@ class TestCLITransition: |
5671 | 5675 |
config: Any, |
5672 | 5676 |
) -> None: |
5673 | 5677 |
"""Migrating an invalid old settings file fails.""" |
5674 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5678 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5675 | 5679 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5676 | 5680 |
# with-statements. |
5677 | 5681 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5678 | 5682 |
with contextlib.ExitStack() as stack: |
5679 | 5683 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5680 | 5684 |
stack.enter_context( |
5681 |
- tests.isolated_config( |
|
5685 |
+ tests.machinery.pytest.isolated_config( |
|
5682 | 5686 |
monkeypatch=monkeypatch, |
5683 | 5687 |
runner=runner, |
5684 | 5688 |
) |
... | ... |
@@ -5697,33 +5701,33 @@ class TestCLITransition: |
5697 | 5701 |
) -> None: |
5698 | 5702 |
"""Forwarding arguments from "export" to "export vault" works.""" |
5699 | 5703 |
pytest.importorskip("cryptography", minversion="38.0") |
5700 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5704 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5701 | 5705 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5702 | 5706 |
# with-statements. |
5703 | 5707 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5704 | 5708 |
with contextlib.ExitStack() as stack: |
5705 | 5709 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5706 | 5710 |
stack.enter_context( |
5707 |
- tests.isolated_vault_exporter_config( |
|
5711 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
5708 | 5712 |
monkeypatch=monkeypatch, |
5709 | 5713 |
runner=runner, |
5710 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
5711 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
5714 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
5715 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
5712 | 5716 |
) |
5713 | 5717 |
) |
5714 |
- monkeypatch.setenv("VAULT_KEY", tests.VAULT_MASTER_KEY) |
|
5718 |
+ monkeypatch.setenv("VAULT_KEY", tests.data.VAULT_MASTER_KEY) |
|
5715 | 5719 |
result = runner.invoke( |
5716 | 5720 |
cli.derivepassphrase, |
5717 | 5721 |
["export", "VAULT_PATH"], |
5718 | 5722 |
) |
5719 | 5723 |
assert result.clean_exit(empty_stderr=False), "expected clean exit" |
5720 |
- assert tests.deprecation_warning_emitted( |
|
5724 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5721 | 5725 |
"A subcommand will be required here in v1.0", caplog.record_tuples |
5722 | 5726 |
) |
5723 |
- assert tests.deprecation_warning_emitted( |
|
5727 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5724 | 5728 |
'Defaulting to subcommand "vault"', caplog.record_tuples |
5725 | 5729 |
) |
5726 |
- assert json.loads(result.stdout) == tests.VAULT_V03_CONFIG_DATA |
|
5730 |
+ assert json.loads(result.stdout) == tests.data.VAULT_V03_CONFIG_DATA |
|
5727 | 5731 |
|
5728 | 5732 |
def test_201_forward_export_vault_empty_commandline( |
5729 | 5733 |
self, |
... | ... |
@@ -5731,14 +5735,14 @@ class TestCLITransition: |
5731 | 5735 |
) -> None: |
5732 | 5736 |
"""Deferring from "export" to "export vault" works.""" |
5733 | 5737 |
pytest.importorskip("cryptography", minversion="38.0") |
5734 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5738 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5735 | 5739 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5736 | 5740 |
# with-statements. |
5737 | 5741 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5738 | 5742 |
with contextlib.ExitStack() as stack: |
5739 | 5743 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5740 | 5744 |
stack.enter_context( |
5741 |
- tests.isolated_config( |
|
5745 |
+ tests.machinery.pytest.isolated_config( |
|
5742 | 5746 |
monkeypatch=monkeypatch, |
5743 | 5747 |
runner=runner, |
5744 | 5748 |
) |
... | ... |
@@ -5747,10 +5751,10 @@ class TestCLITransition: |
5747 | 5751 |
cli.derivepassphrase, |
5748 | 5752 |
["export"], |
5749 | 5753 |
) |
5750 |
- assert tests.deprecation_warning_emitted( |
|
5754 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5751 | 5755 |
"A subcommand will be required here in v1.0", caplog.record_tuples |
5752 | 5756 |
) |
5753 |
- assert tests.deprecation_warning_emitted( |
|
5757 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5754 | 5758 |
'Defaulting to subcommand "vault"', caplog.record_tuples |
5755 | 5759 |
) |
5756 | 5760 |
assert result.error_exit(error="Missing argument 'PATH'"), ( |
... | ... |
@@ -5766,20 +5770,20 @@ class TestCLITransition: |
5766 | 5770 |
"""Forwarding arguments from top-level to "vault" works.""" |
5767 | 5771 |
option = f"--{charset_name}" |
5768 | 5772 |
charset = vault.Vault.CHARSETS[charset_name].decode("ascii") |
5769 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5773 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5770 | 5774 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5771 | 5775 |
# with-statements. |
5772 | 5776 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5773 | 5777 |
with contextlib.ExitStack() as stack: |
5774 | 5778 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5775 | 5779 |
stack.enter_context( |
5776 |
- tests.isolated_config( |
|
5780 |
+ tests.machinery.pytest.isolated_config( |
|
5777 | 5781 |
monkeypatch=monkeypatch, |
5778 | 5782 |
runner=runner, |
5779 | 5783 |
) |
5780 | 5784 |
) |
5781 | 5785 |
monkeypatch.setattr( |
5782 |
- cli_helpers, "prompt_for_passphrase", tests.auto_prompt |
|
5786 |
+ cli_helpers, "prompt_for_passphrase", tests.data.callables.auto_prompt |
|
5783 | 5787 |
) |
5784 | 5788 |
result = runner.invoke( |
5785 | 5789 |
cli.derivepassphrase, |
... | ... |
@@ -5788,10 +5792,10 @@ class TestCLITransition: |
5788 | 5792 |
catch_exceptions=False, |
5789 | 5793 |
) |
5790 | 5794 |
assert result.clean_exit(empty_stderr=False), "expected clean exit" |
5791 |
- assert tests.deprecation_warning_emitted( |
|
5795 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5792 | 5796 |
"A subcommand will be required here in v1.0", caplog.record_tuples |
5793 | 5797 |
) |
5794 |
- assert tests.deprecation_warning_emitted( |
|
5798 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5795 | 5799 |
'Defaulting to subcommand "vault"', caplog.record_tuples |
5796 | 5800 |
) |
5797 | 5801 |
for c in charset: |
... | ... |
@@ -5804,14 +5808,14 @@ class TestCLITransition: |
5804 | 5808 |
caplog: pytest.LogCaptureFixture, |
5805 | 5809 |
) -> None: |
5806 | 5810 |
"""Deferring from top-level to "vault" works.""" |
5807 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5811 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5808 | 5812 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5809 | 5813 |
# with-statements. |
5810 | 5814 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5811 | 5815 |
with contextlib.ExitStack() as stack: |
5812 | 5816 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5813 | 5817 |
stack.enter_context( |
5814 |
- tests.isolated_config( |
|
5818 |
+ tests.machinery.pytest.isolated_config( |
|
5815 | 5819 |
monkeypatch=monkeypatch, |
5816 | 5820 |
runner=runner, |
5817 | 5821 |
) |
... | ... |
@@ -5822,10 +5826,10 @@ class TestCLITransition: |
5822 | 5826 |
input=DUMMY_PASSPHRASE, |
5823 | 5827 |
catch_exceptions=False, |
5824 | 5828 |
) |
5825 |
- assert tests.deprecation_warning_emitted( |
|
5829 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5826 | 5830 |
"A subcommand will be required here in v1.0", caplog.record_tuples |
5827 | 5831 |
) |
5828 |
- assert tests.deprecation_warning_emitted( |
|
5832 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5829 | 5833 |
'Defaulting to subcommand "vault"', caplog.record_tuples |
5830 | 5834 |
) |
5831 | 5835 |
assert result.error_exit( |
... | ... |
@@ -5838,14 +5842,14 @@ class TestCLITransition: |
5838 | 5842 |
) -> None: |
5839 | 5843 |
"""Exporting from (and migrating) the old settings file works.""" |
5840 | 5844 |
caplog.set_level(logging.INFO) |
5841 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5845 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5842 | 5846 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5843 | 5847 |
# with-statements. |
5844 | 5848 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5845 | 5849 |
with contextlib.ExitStack() as stack: |
5846 | 5850 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5847 | 5851 |
stack.enter_context( |
5848 |
- tests.isolated_config( |
|
5852 |
+ tests.machinery.pytest.isolated_config( |
|
5849 | 5853 |
monkeypatch=monkeypatch, |
5850 | 5854 |
runner=runner, |
5851 | 5855 |
) |
... | ... |
@@ -5866,10 +5870,10 @@ class TestCLITransition: |
5866 | 5870 |
catch_exceptions=False, |
5867 | 5871 |
) |
5868 | 5872 |
assert result.clean_exit(), "expected clean exit" |
5869 |
- assert tests.deprecation_warning_emitted( |
|
5873 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5870 | 5874 |
"v0.1-style config file", caplog.record_tuples |
5871 | 5875 |
), "expected known warning message in stderr" |
5872 |
- assert tests.deprecation_info_emitted( |
|
5876 |
+ assert tests.machinery.deprecation_info_emitted( |
|
5873 | 5877 |
"Successfully migrated to ", caplog.record_tuples |
5874 | 5878 |
), "expected known warning message in stderr" |
5875 | 5879 |
|
... | ... |
@@ -5878,14 +5882,14 @@ class TestCLITransition: |
5878 | 5882 |
caplog: pytest.LogCaptureFixture, |
5879 | 5883 |
) -> None: |
5880 | 5884 |
"""Exporting from (and not migrating) the old settings file fails.""" |
5881 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5885 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5882 | 5886 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5883 | 5887 |
# with-statements. |
5884 | 5888 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5885 | 5889 |
with contextlib.ExitStack() as stack: |
5886 | 5890 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5887 | 5891 |
stack.enter_context( |
5888 |
- tests.isolated_config( |
|
5892 |
+ tests.machinery.pytest.isolated_config( |
|
5889 | 5893 |
monkeypatch=monkeypatch, |
5890 | 5894 |
runner=runner, |
5891 | 5895 |
) |
... | ... |
@@ -5916,10 +5920,10 @@ class TestCLITransition: |
5916 | 5920 |
catch_exceptions=False, |
5917 | 5921 |
) |
5918 | 5922 |
assert result.clean_exit(), "expected clean exit" |
5919 |
- assert tests.deprecation_warning_emitted( |
|
5923 |
+ assert tests.machinery.deprecation_warning_emitted( |
|
5920 | 5924 |
"v0.1-style config file", caplog.record_tuples |
5921 | 5925 |
), "expected known warning message in stderr" |
5922 |
- assert tests.warning_emitted( |
|
5926 |
+ assert tests.machinery.warning_emitted( |
|
5923 | 5927 |
"Failed to migrate to ", caplog.record_tuples |
5924 | 5928 |
), "expected known warning message in stderr" |
5925 | 5929 |
|
... | ... |
@@ -5928,14 +5932,14 @@ class TestCLITransition: |
5928 | 5932 |
) -> None: |
5929 | 5933 |
"""Completing service names from the old settings file works.""" |
5930 | 5934 |
config = {"services": {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()}} |
5931 |
- runner = tests.CliRunner(mix_stderr=False) |
|
5935 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
5932 | 5936 |
# TODO(the-13th-letter): Rewrite using parenthesized |
5933 | 5937 |
# with-statements. |
5934 | 5938 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
5935 | 5939 |
with contextlib.ExitStack() as stack: |
5936 | 5940 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
5937 | 5941 |
stack.enter_context( |
5938 |
- tests.isolated_vault_config( |
|
5942 |
+ tests.machinery.pytest.isolated_vault_config( |
|
5939 | 5943 |
monkeypatch=monkeypatch, |
5940 | 5944 |
runner=runner, |
5941 | 5945 |
vault_config=config, |
... | ... |
@@ -5990,7 +5994,7 @@ def build_reduced_vault_config_settings( |
5990 | 5994 |
|
5991 | 5995 |
SERVICES_STRATEGY = strategies.builds( |
5992 | 5996 |
build_reduced_vault_config_settings, |
5993 |
- tests.vault_full_service_config(), |
|
5997 |
+ tests.machinery.hypothesis.vault_full_service_config(), |
|
5994 | 5998 |
strategies.sets( |
5995 | 5999 |
strategies.sampled_from(VALID_PROPERTIES), |
5996 | 6000 |
max_size=7, |
... | ... |
@@ -6084,13 +6088,13 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
6084 | 6088 |
def __init__(self) -> None: |
6085 | 6089 |
"""Initialize self, set up context managers and enter them.""" |
6086 | 6090 |
super().__init__() |
6087 |
- self.runner = tests.CliRunner(mix_stderr=False) |
|
6091 |
+ self.runner = tests.machinery.CliRunner(mix_stderr=False) |
|
6088 | 6092 |
self.exit_stack = contextlib.ExitStack().__enter__() |
6089 | 6093 |
self.monkeypatch = self.exit_stack.enter_context( |
6090 | 6094 |
pytest.MonkeyPatch().context() |
6091 | 6095 |
) |
6092 | 6096 |
self.isolated_config = self.exit_stack.enter_context( |
6093 |
- tests.isolated_vault_config( |
|
6097 |
+ tests.machinery.pytest.isolated_vault_config( |
|
6094 | 6098 |
monkeypatch=self.monkeypatch, |
6095 | 6099 |
runner=self.runner, |
6096 | 6100 |
vault_config={"services": {}}, |
... | ... |
@@ -6511,7 +6515,7 @@ def run_actions_handler( |
6511 | 6515 |
timeout=timeout, |
6512 | 6516 |
), |
6513 | 6517 |
) |
6514 |
- runner = tests.CliRunner(mix_stderr=False) |
|
6518 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
6515 | 6519 |
try: |
6516 | 6520 |
result = runner.invoke( |
6517 | 6521 |
cli.derivepassphrase_vault, |
... | ... |
@@ -6542,7 +6546,7 @@ def run_actions_handler( |
6542 | 6546 |
|
6543 | 6547 |
|
6544 | 6548 |
@hypothesis.settings( |
6545 |
- stateful_step_count=tests.get_concurrency_step_count(), |
|
6549 |
+ stateful_step_count=tests.machinery.hypothesis.get_concurrency_step_count(), |
|
6546 | 6550 |
deadline=None, |
6547 | 6551 |
) |
6548 | 6552 |
class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
... | ... |
@@ -6723,7 +6727,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
6723 | 6727 |
settings = FakeConfigurationMutexStateMachine.TestCase.settings |
6724 | 6728 |
except AttributeError: # pragma: no cover |
6725 | 6729 |
settings = None |
6726 |
- self.step_count = tests.get_concurrency_step_count(settings) |
|
6730 |
+ self.step_count = tests.machinery.hypothesis.get_concurrency_step_count(settings) |
|
6727 | 6731 |
|
6728 | 6732 |
@stateful.initialize( |
6729 | 6733 |
target=configuration, |
... | ... |
@@ -7002,7 +7006,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
7002 | 7006 |
mp = multiprocessing.get_context() |
7003 | 7007 |
# Coverage tracking writes coverage data to the current working |
7004 | 7008 |
# directory, but because the subprocesses are spawned within the |
7005 |
- # `tests.isolated_vault_config` context manager, their starting |
|
7009 |
+ # `tests.machinery.pytest.isolated_vault_config` context manager, their starting |
|
7006 | 7010 |
# working directory is the isolated one, not the original one. |
7007 | 7011 |
orig_cwd = pathlib.Path.cwd() |
7008 | 7012 |
|
... | ... |
@@ -7021,10 +7025,10 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
7021 | 7025 |
|
7022 | 7026 |
stack = contextlib.ExitStack() |
7023 | 7027 |
with stack: |
7024 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7028 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7025 | 7029 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7026 | 7030 |
stack.enter_context( |
7027 |
- tests.isolated_vault_config( |
|
7031 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7028 | 7032 |
monkeypatch=monkeypatch, |
7029 | 7033 |
runner=runner, |
7030 | 7034 |
vault_config={"services": {}}, |
... | ... |
@@ -7045,10 +7049,10 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
7045 | 7049 |
) |
7046 | 7050 |
|
7047 | 7051 |
with stack: # noqa: PLR1702 |
7048 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7052 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7049 | 7053 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7050 | 7054 |
stack.enter_context( |
7051 |
- tests.isolated_vault_config( |
|
7055 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7052 | 7056 |
monkeypatch=monkeypatch, |
7053 | 7057 |
runner=runner, |
7054 | 7058 |
vault_config={"services": {}}, |
... | ... |
@@ -7133,7 +7137,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
7133 | 7137 |
break |
7134 | 7138 |
finally: |
7135 | 7139 |
# The subprocesses have this |
7136 |
- # `tests.isolated_vault_config` directory as their |
|
7140 |
+ # `tests.machinery.pytest.isolated_vault_config` directory as their |
|
7137 | 7141 |
# startup and working directory, so systems like |
7138 | 7142 |
# coverage tracking write their data files to this |
7139 | 7143 |
# directory. We need to manually move them back to |
... | ... |
@@ -7228,7 +7232,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
7228 | 7232 |
raise AssertionError() |
7229 | 7233 |
|
7230 | 7234 |
|
7231 |
-TestFakedConfigurationMutex = tests.skip_if_no_multiprocessing_support( |
|
7235 |
+TestFakedConfigurationMutex = tests.machinery.pytest.skip_if_no_multiprocessing_support( |
|
7232 | 7236 |
FakeConfigurationMutexStateMachine.TestCase |
7233 | 7237 |
) |
7234 | 7238 |
"""The [`unittest.TestCase`][] class that will actually be run.""" |
... | ... |
@@ -7350,14 +7354,14 @@ class TestShellCompletion: |
7350 | 7354 |
completions: AbstractSet[str], |
7351 | 7355 |
) -> None: |
7352 | 7356 |
"""Our completion machinery works for vault service names.""" |
7353 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7357 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7354 | 7358 |
# TODO(the-13th-letter): Rewrite using parenthesized |
7355 | 7359 |
# with-statements. |
7356 | 7360 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
7357 | 7361 |
with contextlib.ExitStack() as stack: |
7358 | 7362 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7359 | 7363 |
stack.enter_context( |
7360 |
- tests.isolated_vault_config( |
|
7364 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7361 | 7365 |
monkeypatch=monkeypatch, |
7362 | 7366 |
runner=runner, |
7363 | 7367 |
vault_config=config, |
... | ... |
@@ -7382,14 +7386,14 @@ class TestShellCompletion: |
7382 | 7386 |
results: list[str | click.shell_completion.CompletionItem], |
7383 | 7387 |
) -> None: |
7384 | 7388 |
"""Custom completion functions work for all shells.""" |
7385 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7389 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7386 | 7390 |
# TODO(the-13th-letter): Rewrite using parenthesized |
7387 | 7391 |
# with-statements. |
7388 | 7392 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
7389 | 7393 |
with contextlib.ExitStack() as stack: |
7390 | 7394 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7391 | 7395 |
stack.enter_context( |
7392 |
- tests.isolated_vault_config( |
|
7396 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7393 | 7397 |
monkeypatch=monkeypatch, |
7394 | 7398 |
runner=runner, |
7395 | 7399 |
vault_config=config, |
... | ... |
@@ -7444,14 +7448,14 @@ class TestShellCompletion: |
7444 | 7448 |
) -> None: |
7445 | 7449 |
"""Completion skips incompletable items.""" |
7446 | 7450 |
vault_config = config if mode == "config" else {"services": {}} |
7447 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7451 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7448 | 7452 |
# TODO(the-13th-letter): Rewrite using parenthesized |
7449 | 7453 |
# with-statements. |
7450 | 7454 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
7451 | 7455 |
with contextlib.ExitStack() as stack: |
7452 | 7456 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7453 | 7457 |
stack.enter_context( |
7454 |
- tests.isolated_vault_config( |
|
7458 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7455 | 7459 |
monkeypatch=monkeypatch, |
7456 | 7460 |
runner=runner, |
7457 | 7461 |
vault_config=vault_config, |
... | ... |
@@ -7471,10 +7475,10 @@ class TestShellCompletion: |
7471 | 7475 |
input=json.dumps(config), |
7472 | 7476 |
) |
7473 | 7477 |
assert result.clean_exit(), "expected clean exit" |
7474 |
- assert tests.warning_emitted( |
|
7478 |
+ assert tests.machinery.warning_emitted( |
|
7475 | 7479 |
"contains an ASCII control character", caplog.record_tuples |
7476 | 7480 |
), "expected known warning message in stderr" |
7477 |
- assert tests.warning_emitted( |
|
7481 |
+ assert tests.machinery.warning_emitted( |
|
7478 | 7482 |
"not be available for completion", caplog.record_tuples |
7479 | 7483 |
), "expected known warning message in stderr" |
7480 | 7484 |
assert cli_helpers.load_config() == config |
... | ... |
@@ -7485,14 +7489,14 @@ class TestShellCompletion: |
7485 | 7489 |
self, |
7486 | 7490 |
) -> None: |
7487 | 7491 |
"""Service name completion quietly fails on missing configuration.""" |
7488 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7492 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7489 | 7493 |
# TODO(the-13th-letter): Rewrite using parenthesized |
7490 | 7494 |
# with-statements. |
7491 | 7495 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
7492 | 7496 |
with contextlib.ExitStack() as stack: |
7493 | 7497 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7494 | 7498 |
stack.enter_context( |
7495 |
- tests.isolated_vault_config( |
|
7499 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7496 | 7500 |
monkeypatch=monkeypatch, |
7497 | 7501 |
runner=runner, |
7498 | 7502 |
vault_config={ |
... | ... |
@@ -7515,14 +7519,14 @@ class TestShellCompletion: |
7515 | 7519 |
exc_type: type[Exception], |
7516 | 7520 |
) -> None: |
7517 | 7521 |
"""Service name completion quietly fails on configuration errors.""" |
7518 |
- runner = tests.CliRunner(mix_stderr=False) |
|
7522 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
7519 | 7523 |
# TODO(the-13th-letter): Rewrite using parenthesized |
7520 | 7524 |
# with-statements. |
7521 | 7525 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
7522 | 7526 |
with contextlib.ExitStack() as stack: |
7523 | 7527 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
7524 | 7528 |
stack.enter_context( |
7525 |
- tests.isolated_vault_config( |
|
7529 |
+ tests.machinery.pytest.isolated_vault_config( |
|
7526 | 7530 |
monkeypatch=monkeypatch, |
7527 | 7531 |
runner=runner, |
7528 | 7532 |
vault_config={ |
... | ... |
@@ -15,7 +15,9 @@ import hypothesis |
15 | 15 |
import pytest |
16 | 16 |
from hypothesis import strategies |
17 | 17 |
|
18 |
-import tests |
|
18 |
+import tests.data |
|
19 |
+import tests.machinery |
|
20 |
+import tests.machinery.pytest |
|
19 | 21 |
from derivepassphrase import _types, cli, exporter |
20 | 22 |
from derivepassphrase.exporter import storeroom, vault_native |
21 | 23 |
|
... | ... |
@@ -47,27 +49,27 @@ class Parametrize(types.SimpleNamespace): |
47 | 49 |
["config", "format", "config_data"], |
48 | 50 |
[ |
49 | 51 |
pytest.param( |
50 |
- tests.VAULT_V02_CONFIG, |
|
52 |
+ tests.data.VAULT_V02_CONFIG, |
|
51 | 53 |
"v0.2", |
52 |
- tests.VAULT_V02_CONFIG_DATA, |
|
54 |
+ tests.data.VAULT_V02_CONFIG_DATA, |
|
53 | 55 |
id="V02_CONFIG-v0.2", |
54 | 56 |
), |
55 | 57 |
pytest.param( |
56 |
- tests.VAULT_V02_CONFIG, |
|
58 |
+ tests.data.VAULT_V02_CONFIG, |
|
57 | 59 |
"v0.3", |
58 | 60 |
exporter.NotAVaultConfigError, |
59 | 61 |
id="V02_CONFIG-v0.3", |
60 | 62 |
), |
61 | 63 |
pytest.param( |
62 |
- tests.VAULT_V03_CONFIG, |
|
64 |
+ tests.data.VAULT_V03_CONFIG, |
|
63 | 65 |
"v0.2", |
64 | 66 |
exporter.NotAVaultConfigError, |
65 | 67 |
id="V03_CONFIG-v0.2", |
66 | 68 |
), |
67 | 69 |
pytest.param( |
68 |
- tests.VAULT_V03_CONFIG, |
|
70 |
+ tests.data.VAULT_V03_CONFIG, |
|
69 | 71 |
"v0.3", |
70 |
- tests.VAULT_V03_CONFIG_DATA, |
|
72 |
+ tests.data.VAULT_V03_CONFIG_DATA, |
|
71 | 73 |
id="V03_CONFIG-v0.3", |
72 | 74 |
), |
73 | 75 |
], |
... | ... |
@@ -76,15 +78,15 @@ class Parametrize(types.SimpleNamespace): |
76 | 78 |
["config", "parser_class", "config_data"], |
77 | 79 |
[ |
78 | 80 |
pytest.param( |
79 |
- tests.VAULT_V02_CONFIG, |
|
81 |
+ tests.data.VAULT_V02_CONFIG, |
|
80 | 82 |
vault_native.VaultNativeV02ConfigParser, |
81 |
- tests.VAULT_V02_CONFIG_DATA, |
|
83 |
+ tests.data.VAULT_V02_CONFIG_DATA, |
|
82 | 84 |
id="0.2", |
83 | 85 |
), |
84 | 86 |
pytest.param( |
85 |
- tests.VAULT_V03_CONFIG, |
|
87 |
+ tests.data.VAULT_V03_CONFIG, |
|
86 | 88 |
vault_native.VaultNativeV03ConfigParser, |
87 |
- tests.VAULT_V03_CONFIG_DATA, |
|
89 |
+ tests.data.VAULT_V03_CONFIG_DATA, |
|
88 | 90 |
id="0.3", |
89 | 91 |
), |
90 | 92 |
], |
... | ... |
@@ -134,14 +136,14 @@ class Parametrize(types.SimpleNamespace): |
134 | 136 |
"key", |
135 | 137 |
[ |
136 | 138 |
None, |
137 |
- pytest.param(tests.VAULT_MASTER_KEY, id="str"), |
|
138 |
- pytest.param(tests.VAULT_MASTER_KEY.encode("ascii"), id="bytes"), |
|
139 |
+ pytest.param(tests.data.VAULT_MASTER_KEY, id="str"), |
|
140 |
+ pytest.param(tests.data.VAULT_MASTER_KEY.encode("ascii"), id="bytes"), |
|
139 | 141 |
pytest.param( |
140 |
- bytearray(tests.VAULT_MASTER_KEY.encode("ascii")), |
|
142 |
+ bytearray(tests.data.VAULT_MASTER_KEY.encode("ascii")), |
|
141 | 143 |
id="bytearray", |
142 | 144 |
), |
143 | 145 |
pytest.param( |
144 |
- memoryview(tests.VAULT_MASTER_KEY.encode("ascii")), |
|
146 |
+ memoryview(tests.data.VAULT_MASTER_KEY.encode("ascii")), |
|
145 | 147 |
id="memoryview", |
146 | 148 |
), |
147 | 149 |
], |
... | ... |
@@ -151,22 +153,22 @@ class Parametrize(types.SimpleNamespace): |
151 | 153 |
["zipped_config", "error_text"], |
152 | 154 |
[ |
153 | 155 |
pytest.param( |
154 |
- tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED, |
|
156 |
+ tests.data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED, |
|
155 | 157 |
"Object key mismatch", |
156 | 158 |
id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED", |
157 | 159 |
), |
158 | 160 |
pytest.param( |
159 |
- tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2, |
|
161 |
+ tests.data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2, |
|
160 | 162 |
"Directory index is not actually an index", |
161 | 163 |
id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2", |
162 | 164 |
), |
163 | 165 |
pytest.param( |
164 |
- tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3, |
|
166 |
+ tests.data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3, |
|
165 | 167 |
"Directory index is not actually an index", |
166 | 168 |
id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3", |
167 | 169 |
), |
168 | 170 |
pytest.param( |
169 |
- tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4, |
|
171 |
+ tests.data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4, |
|
170 | 172 |
"Object key mismatch", |
171 | 173 |
id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4", |
172 | 174 |
), |
... | ... |
@@ -185,51 +187,51 @@ class TestCLI: |
185 | 187 |
[`exporter.get_vault_path`][] for details. |
186 | 188 |
|
187 | 189 |
""" |
188 |
- runner = tests.CliRunner(mix_stderr=False) |
|
190 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
189 | 191 |
# TODO(the-13th-letter): Rewrite using parenthesized |
190 | 192 |
# with-statements. |
191 | 193 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
192 | 194 |
with contextlib.ExitStack() as stack: |
193 | 195 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
194 | 196 |
stack.enter_context( |
195 |
- tests.isolated_vault_exporter_config( |
|
197 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
196 | 198 |
monkeypatch=monkeypatch, |
197 | 199 |
runner=runner, |
198 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
199 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
200 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
201 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
200 | 202 |
) |
201 | 203 |
) |
202 |
- monkeypatch.setenv("VAULT_KEY", tests.VAULT_MASTER_KEY) |
|
204 |
+ monkeypatch.setenv("VAULT_KEY", tests.data.VAULT_MASTER_KEY) |
|
203 | 205 |
result = runner.invoke( |
204 | 206 |
cli.derivepassphrase_export_vault, |
205 | 207 |
["VAULT_PATH"], |
206 | 208 |
) |
207 | 209 |
assert result.clean_exit(empty_stderr=True), "expected clean exit" |
208 |
- assert json.loads(result.stdout) == tests.VAULT_V03_CONFIG_DATA |
|
210 |
+ assert json.loads(result.stdout) == tests.data.VAULT_V03_CONFIG_DATA |
|
209 | 211 |
|
210 | 212 |
def test_201_key_parameter(self) -> None: |
211 | 213 |
"""The `--key` option is supported.""" |
212 |
- runner = tests.CliRunner(mix_stderr=False) |
|
214 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
213 | 215 |
# TODO(the-13th-letter): Rewrite using parenthesized |
214 | 216 |
# with-statements. |
215 | 217 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
216 | 218 |
with contextlib.ExitStack() as stack: |
217 | 219 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
218 | 220 |
stack.enter_context( |
219 |
- tests.isolated_vault_exporter_config( |
|
221 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
220 | 222 |
monkeypatch=monkeypatch, |
221 | 223 |
runner=runner, |
222 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
224 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
223 | 225 |
) |
224 | 226 |
) |
225 | 227 |
result = runner.invoke( |
226 | 228 |
cli.derivepassphrase_export_vault, |
227 |
- ["-k", tests.VAULT_MASTER_KEY, ".vault"], |
|
229 |
+ ["-k", tests.data.VAULT_MASTER_KEY, ".vault"], |
|
228 | 230 |
) |
229 | 231 |
assert result.clean_exit(empty_stderr=True), "expected clean exit" |
230 |
- assert json.loads(result.stdout) == tests.VAULT_V03_CONFIG_DATA |
|
232 |
+ assert json.loads(result.stdout) == tests.data.VAULT_V03_CONFIG_DATA |
|
231 | 233 |
|
232 |
- @tests.Parametrize.VAULT_CONFIG_FORMATS_DATA |
|
234 |
+ @tests.machinery.pytest.Parametrize.VAULT_CONFIG_FORMATS_DATA |
|
233 | 235 |
def test_210_load_vault_v02_v03_storeroom( |
234 | 236 |
self, |
235 | 237 |
config: str | bytes, |
... | ... |
@@ -242,14 +244,14 @@ class TestCLI: |
242 | 244 |
vault` to only attempt decoding in that named format. |
243 | 245 |
|
244 | 246 |
""" |
245 |
- runner = tests.CliRunner(mix_stderr=False) |
|
247 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
246 | 248 |
# TODO(the-13th-letter): Rewrite using parenthesized |
247 | 249 |
# with-statements. |
248 | 250 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
249 | 251 |
with contextlib.ExitStack() as stack: |
250 | 252 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
251 | 253 |
stack.enter_context( |
252 |
- tests.isolated_vault_exporter_config( |
|
254 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
253 | 255 |
monkeypatch=monkeypatch, |
254 | 256 |
runner=runner, |
255 | 257 |
vault_config=config, |
... | ... |
@@ -257,7 +259,7 @@ class TestCLI: |
257 | 259 |
) |
258 | 260 |
result = runner.invoke( |
259 | 261 |
cli.derivepassphrase_export_vault, |
260 |
- ["-f", format, "-k", tests.VAULT_MASTER_KEY, "VAULT_PATH"], |
|
262 |
+ ["-f", format, "-k", tests.data.VAULT_MASTER_KEY, "VAULT_PATH"], |
|
261 | 263 |
) |
262 | 264 |
assert result.clean_exit(empty_stderr=True), "expected clean exit" |
263 | 265 |
assert json.loads(result.stdout) == config_data |
... | ... |
@@ -270,18 +272,18 @@ class TestCLI: |
270 | 272 |
caplog: pytest.LogCaptureFixture, |
271 | 273 |
) -> None: |
272 | 274 |
"""Fail when trying to decode non-existant files/directories.""" |
273 |
- runner = tests.CliRunner(mix_stderr=False) |
|
275 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
274 | 276 |
# TODO(the-13th-letter): Rewrite using parenthesized |
275 | 277 |
# with-statements. |
276 | 278 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
277 | 279 |
with contextlib.ExitStack() as stack: |
278 | 280 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
279 | 281 |
stack.enter_context( |
280 |
- tests.isolated_vault_exporter_config( |
|
282 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
281 | 283 |
monkeypatch=monkeypatch, |
282 | 284 |
runner=runner, |
283 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
284 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
285 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
286 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
285 | 287 |
) |
286 | 288 |
) |
287 | 289 |
result = runner.invoke( |
... | ... |
@@ -295,25 +297,25 @@ class TestCLI: |
295 | 297 |
), |
296 | 298 |
record_tuples=caplog.record_tuples, |
297 | 299 |
), "expected error exit and known error message" |
298 |
- assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
300 |
+ assert tests.data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
299 | 301 |
|
300 | 302 |
def test_302_vault_config_invalid( |
301 | 303 |
self, |
302 | 304 |
caplog: pytest.LogCaptureFixture, |
303 | 305 |
) -> None: |
304 | 306 |
"""Fail to parse invalid vault configurations (files).""" |
305 |
- runner = tests.CliRunner(mix_stderr=False) |
|
307 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
306 | 308 |
# TODO(the-13th-letter): Rewrite using parenthesized |
307 | 309 |
# with-statements. |
308 | 310 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
309 | 311 |
with contextlib.ExitStack() as stack: |
310 | 312 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
311 | 313 |
stack.enter_context( |
312 |
- tests.isolated_vault_exporter_config( |
|
314 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
313 | 315 |
monkeypatch=monkeypatch, |
314 | 316 |
runner=runner, |
315 | 317 |
vault_config="", |
316 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
318 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
317 | 319 |
) |
318 | 320 |
) |
319 | 321 |
result = runner.invoke( |
... | ... |
@@ -324,25 +326,25 @@ class TestCLI: |
324 | 326 |
error="Cannot parse '.vault' as a valid vault-native config", |
325 | 327 |
record_tuples=caplog.record_tuples, |
326 | 328 |
), "expected error exit and known error message" |
327 |
- assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
329 |
+ assert tests.data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
328 | 330 |
|
329 | 331 |
def test_302a_vault_config_invalid_just_a_directory( |
330 | 332 |
self, |
331 | 333 |
caplog: pytest.LogCaptureFixture, |
332 | 334 |
) -> None: |
333 | 335 |
"""Fail to parse invalid vault configurations (directories).""" |
334 |
- runner = tests.CliRunner(mix_stderr=False) |
|
336 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
335 | 337 |
# TODO(the-13th-letter): Rewrite using parenthesized |
336 | 338 |
# with-statements. |
337 | 339 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
338 | 340 |
with contextlib.ExitStack() as stack: |
339 | 341 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
340 | 342 |
stack.enter_context( |
341 |
- tests.isolated_vault_exporter_config( |
|
343 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
342 | 344 |
monkeypatch=monkeypatch, |
343 | 345 |
runner=runner, |
344 | 346 |
vault_config="", |
345 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
347 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
346 | 348 |
) |
347 | 349 |
) |
348 | 350 |
p = pathlib.Path(".vault") |
... | ... |
@@ -356,25 +358,25 @@ class TestCLI: |
356 | 358 |
error="Cannot parse '.vault' as a valid vault-native config", |
357 | 359 |
record_tuples=caplog.record_tuples, |
358 | 360 |
), "expected error exit and known error message" |
359 |
- assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
361 |
+ assert tests.data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
360 | 362 |
|
361 | 363 |
def test_403_invalid_vault_config_bad_signature( |
362 | 364 |
self, |
363 | 365 |
caplog: pytest.LogCaptureFixture, |
364 | 366 |
) -> None: |
365 | 367 |
"""Fail to parse vault configurations with invalid integrity checks.""" |
366 |
- runner = tests.CliRunner(mix_stderr=False) |
|
368 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
367 | 369 |
# TODO(the-13th-letter): Rewrite using parenthesized |
368 | 370 |
# with-statements. |
369 | 371 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
370 | 372 |
with contextlib.ExitStack() as stack: |
371 | 373 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
372 | 374 |
stack.enter_context( |
373 |
- tests.isolated_vault_exporter_config( |
|
375 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
374 | 376 |
monkeypatch=monkeypatch, |
375 | 377 |
runner=runner, |
376 |
- vault_config=tests.VAULT_V02_CONFIG, |
|
377 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
378 |
+ vault_config=tests.data.VAULT_V02_CONFIG, |
|
379 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
378 | 380 |
) |
379 | 381 |
) |
380 | 382 |
result = runner.invoke( |
... | ... |
@@ -385,25 +387,25 @@ class TestCLI: |
385 | 387 |
error="Cannot parse '.vault' as a valid vault-native config", |
386 | 388 |
record_tuples=caplog.record_tuples, |
387 | 389 |
), "expected error exit and known error message" |
388 |
- assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
390 |
+ assert tests.data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
389 | 391 |
|
390 | 392 |
def test_500_vault_config_invalid_internal( |
391 | 393 |
self, |
392 | 394 |
caplog: pytest.LogCaptureFixture, |
393 | 395 |
) -> None: |
394 | 396 |
"""The decoded vault configuration data is valid.""" |
395 |
- runner = tests.CliRunner(mix_stderr=False) |
|
397 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
396 | 398 |
# TODO(the-13th-letter): Rewrite using parenthesized |
397 | 399 |
# with-statements. |
398 | 400 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
399 | 401 |
with contextlib.ExitStack() as stack: |
400 | 402 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
401 | 403 |
stack.enter_context( |
402 |
- tests.isolated_vault_exporter_config( |
|
404 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
403 | 405 |
monkeypatch=monkeypatch, |
404 | 406 |
runner=runner, |
405 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
406 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
407 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
408 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
407 | 409 |
) |
408 | 410 |
) |
409 | 411 |
|
... | ... |
@@ -423,7 +425,7 @@ class TestCLI: |
423 | 425 |
error="Invalid vault config: ", |
424 | 426 |
record_tuples=caplog.record_tuples, |
425 | 427 |
), "expected error exit and known error message" |
426 |
- assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
428 |
+ assert tests.data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr |
|
427 | 429 |
|
428 | 430 |
|
429 | 431 |
class TestStoreroom: |
... | ... |
@@ -444,23 +446,23 @@ class TestStoreroom: |
444 | 446 |
them as well. |
445 | 447 |
|
446 | 448 |
""" |
447 |
- runner = tests.CliRunner(mix_stderr=False) |
|
449 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
448 | 450 |
# TODO(the-13th-letter): Rewrite using parenthesized |
449 | 451 |
# with-statements. |
450 | 452 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
451 | 453 |
with contextlib.ExitStack() as stack: |
452 | 454 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
453 | 455 |
stack.enter_context( |
454 |
- tests.isolated_vault_exporter_config( |
|
456 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
455 | 457 |
monkeypatch=monkeypatch, |
456 | 458 |
runner=runner, |
457 |
- vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
458 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
459 |
+ vault_config=tests.data.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
460 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
459 | 461 |
) |
460 | 462 |
) |
461 | 463 |
assert ( |
462 | 464 |
handler(path, key, format="storeroom") |
463 |
- == tests.VAULT_STOREROOM_CONFIG_DATA |
|
465 |
+ == tests.data.VAULT_STOREROOM_CONFIG_DATA |
|
464 | 466 |
) |
465 | 467 |
|
466 | 468 |
def test_400_decrypt_bucket_item_unknown_version(self) -> None: |
... | ... |
@@ -487,7 +489,7 @@ class TestStoreroom: |
487 | 489 |
wrong shape. |
488 | 490 |
|
489 | 491 |
""" |
490 |
- runner = tests.CliRunner(mix_stderr=False) |
|
492 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
491 | 493 |
master_keys = _types.StoreroomMasterKeys( |
492 | 494 |
encryption_key=bytes(storeroom.KEY_SIZE), |
493 | 495 |
signing_key=bytes(storeroom.KEY_SIZE), |
... | ... |
@@ -499,10 +501,10 @@ class TestStoreroom: |
499 | 501 |
with contextlib.ExitStack() as stack: |
500 | 502 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
501 | 503 |
stack.enter_context( |
502 |
- tests.isolated_vault_exporter_config( |
|
504 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
503 | 505 |
monkeypatch=monkeypatch, |
504 | 506 |
runner=runner, |
505 |
- vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
507 |
+ vault_config=tests.data.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
506 | 508 |
) |
507 | 509 |
) |
508 | 510 |
p = pathlib.Path(".vault", "20") |
... | ... |
@@ -524,18 +526,18 @@ class TestStoreroom: |
524 | 526 |
These include unknown versions, and data of the wrong shape. |
525 | 527 |
|
526 | 528 |
""" |
527 |
- runner = tests.CliRunner(mix_stderr=False) |
|
529 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
528 | 530 |
# TODO(the-13th-letter): Rewrite using parenthesized |
529 | 531 |
# with-statements. |
530 | 532 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
531 | 533 |
with contextlib.ExitStack() as stack: |
532 | 534 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
533 | 535 |
stack.enter_context( |
534 |
- tests.isolated_vault_exporter_config( |
|
536 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
535 | 537 |
monkeypatch=monkeypatch, |
536 | 538 |
runner=runner, |
537 |
- vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
538 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
539 |
+ vault_config=tests.data.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
540 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
539 | 541 |
) |
540 | 542 |
) |
541 | 543 |
p = pathlib.Path(".vault", ".keys") |
... | ... |
@@ -566,18 +568,18 @@ class TestStoreroom: |
566 | 568 |
subdirectories. |
567 | 569 |
|
568 | 570 |
""" |
569 |
- runner = tests.CliRunner(mix_stderr=False) |
|
571 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
570 | 572 |
# TODO(the-13th-letter): Rewrite using parenthesized |
571 | 573 |
# with-statements. |
572 | 574 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
573 | 575 |
with contextlib.ExitStack() as stack: |
574 | 576 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
575 | 577 |
stack.enter_context( |
576 |
- tests.isolated_vault_exporter_config( |
|
578 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
577 | 579 |
monkeypatch=monkeypatch, |
578 | 580 |
runner=runner, |
579 | 581 |
vault_config=zipped_config, |
580 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
582 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
581 | 583 |
) |
582 | 584 |
) |
583 | 585 |
stack.enter_context(pytest.raises(RuntimeError, match=error_text)) |
... | ... |
@@ -668,7 +670,7 @@ class TestVaultNativeConfig: |
668 | 670 |
"""The PBKDF2 helper function works.""" |
669 | 671 |
assert ( |
670 | 672 |
vault_native.VaultNativeConfigParser._pbkdf2( |
671 |
- tests.VAULT_MASTER_KEY.encode("utf-8"), 32, iterations |
|
673 |
+ tests.data.VAULT_MASTER_KEY.encode("utf-8"), 32, iterations |
|
672 | 674 |
) |
673 | 675 |
== result |
674 | 676 |
) |
... | ... |
@@ -692,18 +694,18 @@ class TestVaultNativeConfig: |
692 | 694 |
no longer does. |
693 | 695 |
|
694 | 696 |
""" |
695 |
- runner = tests.CliRunner(mix_stderr=False) |
|
697 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
696 | 698 |
# TODO(the-13th-letter): Rewrite using parenthesized |
697 | 699 |
# with-statements. |
698 | 700 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
699 | 701 |
with contextlib.ExitStack() as stack: |
700 | 702 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
701 | 703 |
stack.enter_context( |
702 |
- tests.isolated_vault_exporter_config( |
|
704 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
703 | 705 |
monkeypatch=monkeypatch, |
704 | 706 |
runner=runner, |
705 | 707 |
vault_config=config, |
706 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
708 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
707 | 709 |
) |
708 | 710 |
) |
709 | 711 |
if isinstance(config_data, type): |
... | ... |
@@ -728,23 +730,23 @@ class TestVaultNativeConfig: |
728 | 730 |
them as well. |
729 | 731 |
|
730 | 732 |
""" |
731 |
- runner = tests.CliRunner(mix_stderr=False) |
|
733 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
732 | 734 |
# TODO(the-13th-letter): Rewrite using parenthesized |
733 | 735 |
# with-statements. |
734 | 736 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
735 | 737 |
with contextlib.ExitStack() as stack: |
736 | 738 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
737 | 739 |
stack.enter_context( |
738 |
- tests.isolated_vault_exporter_config( |
|
740 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
739 | 741 |
monkeypatch=monkeypatch, |
740 | 742 |
runner=runner, |
741 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
742 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
743 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
744 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
743 | 745 |
) |
744 | 746 |
) |
745 | 747 |
assert ( |
746 | 748 |
handler(path, key, format="v0.3") |
747 |
- == tests.VAULT_V03_CONFIG_DATA |
|
749 |
+ == tests.data.VAULT_V03_CONFIG_DATA |
|
748 | 750 |
) |
749 | 751 |
|
750 | 752 |
@Parametrize.VAULT_NATIVE_PARSER_CLASS_DATA |
... | ... |
@@ -763,21 +765,21 @@ class TestVaultNativeConfig: |
763 | 765 |
|
764 | 766 |
return func |
765 | 767 |
|
766 |
- runner = tests.CliRunner(mix_stderr=False) |
|
768 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
767 | 769 |
# TODO(the-13th-letter): Rewrite using parenthesized |
768 | 770 |
# with-statements. |
769 | 771 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
770 | 772 |
with contextlib.ExitStack() as stack: |
771 | 773 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
772 | 774 |
stack.enter_context( |
773 |
- tests.isolated_vault_exporter_config( |
|
775 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
774 | 776 |
monkeypatch=monkeypatch, |
775 | 777 |
runner=runner, |
776 | 778 |
vault_config=config, |
777 | 779 |
) |
778 | 780 |
) |
779 | 781 |
parser = parser_class( |
780 |
- base64.b64decode(config), tests.VAULT_MASTER_KEY |
|
782 |
+ base64.b64decode(config), tests.data.VAULT_MASTER_KEY |
|
781 | 783 |
) |
782 | 784 |
assert parser() == config_data |
783 | 785 |
# Now stub out all functions used to calculate the above result. |
... | ... |
@@ -16,7 +16,9 @@ import hypothesis |
16 | 16 |
import pytest |
17 | 17 |
from hypothesis import strategies |
18 | 18 |
|
19 |
-import tests |
|
19 |
+import tests.data |
|
20 |
+import tests.machinery |
|
21 |
+import tests.machinery.pytest |
|
20 | 22 |
from derivepassphrase import cli, exporter |
21 | 23 |
|
22 | 24 |
if TYPE_CHECKING: |
... | ... |
@@ -205,14 +207,14 @@ class Test001ExporterUtils: |
205 | 207 |
("USER", user), |
206 | 208 |
("USERNAME", username), |
207 | 209 |
] |
208 |
- runner = tests.CliRunner(mix_stderr=False) |
|
210 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
209 | 211 |
# TODO(the-13th-letter): Rewrite using parenthesized |
210 | 212 |
# with-statements. |
211 | 213 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
212 | 214 |
with contextlib.ExitStack() as stack: |
213 | 215 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
214 | 216 |
stack.enter_context( |
215 |
- tests.isolated_vault_exporter_config( |
|
217 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
216 | 218 |
monkeypatch=monkeypatch, runner=runner |
217 | 219 |
) |
218 | 220 |
) |
... | ... |
@@ -232,14 +234,14 @@ class Test001ExporterUtils: |
232 | 234 |
Handle relative paths, absolute paths, and missing paths. |
233 | 235 |
|
234 | 236 |
""" |
235 |
- runner = tests.CliRunner(mix_stderr=False) |
|
237 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
236 | 238 |
# TODO(the-13th-letter): Rewrite using parenthesized |
237 | 239 |
# with-statements. |
238 | 240 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
239 | 241 |
with contextlib.ExitStack() as stack: |
240 | 242 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
241 | 243 |
stack.enter_context( |
242 |
- tests.isolated_vault_exporter_config( |
|
244 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
243 | 245 |
monkeypatch=monkeypatch, runner=runner |
244 | 246 |
) |
245 | 247 |
) |
... | ... |
@@ -377,18 +379,18 @@ class Test002CLI: |
377 | 379 |
|
378 | 380 |
def test_300_invalid_format(self) -> None: |
379 | 381 |
"""Reject invalid vault configuration format names.""" |
380 |
- runner = tests.CliRunner(mix_stderr=False) |
|
382 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
381 | 383 |
# TODO(the-13th-letter): Rewrite using parenthesized |
382 | 384 |
# with-statements. |
383 | 385 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
384 | 386 |
with contextlib.ExitStack() as stack: |
385 | 387 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
386 | 388 |
stack.enter_context( |
387 |
- tests.isolated_vault_exporter_config( |
|
389 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
388 | 390 |
monkeypatch=monkeypatch, |
389 | 391 |
runner=runner, |
390 |
- vault_config=tests.VAULT_V03_CONFIG, |
|
391 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
392 |
+ vault_config=tests.data.VAULT_V03_CONFIG, |
|
393 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
392 | 394 |
) |
393 | 395 |
) |
394 | 396 |
result = runner.invoke( |
... | ... |
@@ -401,8 +403,8 @@ class Test002CLI: |
401 | 403 |
"expected error exit and known error message" |
402 | 404 |
) |
403 | 405 |
|
404 |
- @tests.skip_if_cryptography_support |
|
405 |
- @tests.Parametrize.VAULT_CONFIG_FORMATS_DATA |
|
406 |
+ @tests.machinery.pytest.skip_if_cryptography_support |
|
407 |
+ @tests.machinery.pytest.Parametrize.VAULT_CONFIG_FORMATS_DATA |
|
406 | 408 |
def test_999_no_cryptography_error_message( |
407 | 409 |
self, |
408 | 410 |
caplog: pytest.LogCaptureFixture, |
... | ... |
@@ -412,18 +414,18 @@ class Test002CLI: |
412 | 414 |
) -> None: |
413 | 415 |
"""Abort export call if no cryptography is available.""" |
414 | 416 |
del config_data |
415 |
- runner = tests.CliRunner(mix_stderr=False) |
|
417 |
+ runner = tests.machinery.CliRunner(mix_stderr=False) |
|
416 | 418 |
# TODO(the-13th-letter): Rewrite using parenthesized |
417 | 419 |
# with-statements. |
418 | 420 |
# https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
419 | 421 |
with contextlib.ExitStack() as stack: |
420 | 422 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
421 | 423 |
stack.enter_context( |
422 |
- tests.isolated_vault_exporter_config( |
|
424 |
+ tests.machinery.pytest.isolated_vault_exporter_config( |
|
423 | 425 |
monkeypatch=monkeypatch, |
424 | 426 |
runner=runner, |
425 | 427 |
vault_config=config, |
426 |
- vault_key=tests.VAULT_MASTER_KEY, |
|
428 |
+ vault_key=tests.data.VAULT_MASTER_KEY, |
|
427 | 429 |
) |
428 | 430 |
) |
429 | 431 |
result = runner.invoke( |
... | ... |
@@ -432,6 +434,6 @@ class Test002CLI: |
432 | 434 |
catch_exceptions=False, |
433 | 435 |
) |
434 | 436 |
assert result.error_exit( |
435 |
- error=tests.CANNOT_LOAD_CRYPTOGRAPHY, |
|
437 |
+ error=tests.data.CANNOT_LOAD_CRYPTOGRAPHY, |
|
436 | 438 |
record_tuples=caplog.record_tuples, |
437 | 439 |
), "expected error exit and known error message" |
... | ... |
@@ -25,7 +25,11 @@ import hypothesis |
25 | 25 |
import pytest |
26 | 26 |
from hypothesis import stateful, strategies |
27 | 27 |
|
28 |
-import tests |
|
28 |
+import tests.data |
|
29 |
+import tests.data.callables |
|
30 |
+import tests.machinery |
|
31 |
+import tests.machinery.hypothesis |
|
32 |
+import tests.machinery.pytest |
|
29 | 33 |
from derivepassphrase import _types, ssh_agent, vault |
30 | 34 |
from derivepassphrase._internals import cli_helpers |
31 | 35 |
from derivepassphrase.ssh_agent import socketprovider |
... | ... |
@@ -46,9 +50,9 @@ class Parametrize(types.SimpleNamespace): |
46 | 50 |
pytest.param( |
47 | 51 |
[ |
48 | 52 |
importlib.metadata.EntryPoint( |
49 |
- name=tests.faulty_entry_callable.key, |
|
53 |
+ name=tests.data.faulty_entry_callable.key, |
|
50 | 54 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
51 |
- value="tests: faulty_entry_callable", |
|
55 |
+ value="tests.data: faulty_entry_callable", |
|
52 | 56 |
), |
53 | 57 |
], |
54 | 58 |
id="not-callable", |
... | ... |
@@ -56,9 +60,9 @@ class Parametrize(types.SimpleNamespace): |
56 | 60 |
pytest.param( |
57 | 61 |
[ |
58 | 62 |
importlib.metadata.EntryPoint( |
59 |
- name=tests.faulty_entry_name_exists.key, |
|
63 |
+ name=tests.data.faulty_entry_name_exists.key, |
|
60 | 64 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
61 |
- value="tests: faulty_entry_name_exists", |
|
65 |
+ value="tests.data: faulty_entry_name_exists", |
|
62 | 66 |
), |
63 | 67 |
], |
64 | 68 |
id="name-already-exists", |
... | ... |
@@ -66,9 +70,9 @@ class Parametrize(types.SimpleNamespace): |
66 | 70 |
pytest.param( |
67 | 71 |
[ |
68 | 72 |
importlib.metadata.EntryPoint( |
69 |
- name=tests.faulty_entry_alias_exists.key, |
|
73 |
+ name=tests.data.faulty_entry_alias_exists.key, |
|
70 | 74 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
71 |
- value="tests: faulty_entry_alias_exists", |
|
75 |
+ value="tests.data: faulty_entry_alias_exists", |
|
72 | 76 |
), |
73 | 77 |
], |
74 | 78 |
id="alias-already-exists", |
... | ... |
@@ -81,14 +85,14 @@ class Parametrize(types.SimpleNamespace): |
81 | 85 |
pytest.param( |
82 | 86 |
[ |
83 | 87 |
importlib.metadata.EntryPoint( |
84 |
- name=tests.posix_entry.key, |
|
88 |
+ name=tests.data.posix_entry.key, |
|
85 | 89 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
86 |
- value="tests: posix_entry", |
|
90 |
+ value="tests.data: posix_entry", |
|
87 | 91 |
), |
88 | 92 |
importlib.metadata.EntryPoint( |
89 |
- name=tests.the_annoying_os_entry.key, |
|
93 |
+ name=tests.data.the_annoying_os_entry.key, |
|
90 | 94 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
91 |
- value="tests: the_annoying_os_entry", |
|
95 |
+ value="tests.data: the_annoying_os_entry", |
|
92 | 96 |
), |
93 | 97 |
], |
94 | 98 |
id="existing-entries", |
... | ... |
@@ -96,14 +100,14 @@ class Parametrize(types.SimpleNamespace): |
96 | 100 |
pytest.param( |
97 | 101 |
[ |
98 | 102 |
importlib.metadata.EntryPoint( |
99 |
- name=tests.provider_entry1.key, |
|
103 |
+ name=tests.data.callables.provider_entry1.key, |
|
100 | 104 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
101 |
- value="tests: provider_entry1", |
|
105 |
+ value="tests.data.callables: provider_entry1", |
|
102 | 106 |
), |
103 | 107 |
importlib.metadata.EntryPoint( |
104 |
- name=tests.provider_entry2.key, |
|
108 |
+ name=tests.data.callables.provider_entry2.key, |
|
105 | 109 |
group=socketprovider.SocketProvider.ENTRY_POINT_GROUP_NAME, |
106 |
- value="tests: provider_entry2", |
|
110 |
+ value="tests.data.callables: provider_entry2", |
|
107 | 111 |
), |
108 | 112 |
], |
109 | 113 |
id="new-entries", |
... | ... |
@@ -253,7 +257,7 @@ class Parametrize(types.SimpleNamespace): |
253 | 257 |
id="key-not-loaded", |
254 | 258 |
), |
255 | 259 |
pytest.param( |
256 |
- tests.SUPPORTED_KEYS["ed25519"].public_key_data, |
|
260 |
+ tests.data.SUPPORTED_KEYS["ed25519"].public_key_data, |
|
257 | 261 |
True, |
258 | 262 |
_types.SSH_AGENT.FAILURE, |
259 | 263 |
b"", |
... | ... |
@@ -267,10 +271,10 @@ class Parametrize(types.SimpleNamespace): |
267 | 271 |
["key", "single"], |
268 | 272 |
[ |
269 | 273 |
(value.public_key_data, False) |
270 |
- for value in tests.SUPPORTED_KEYS.values() |
|
274 |
+ for value in tests.data.SUPPORTED_KEYS.values() |
|
271 | 275 |
] |
272 |
- + [(tests.list_keys_singleton()[0].key, True)], |
|
273 |
- ids=[*tests.SUPPORTED_KEYS.keys(), "singleton"], |
|
276 |
+ + [(tests.data.callables.list_keys_singleton()[0].key, True)], |
|
277 |
+ ids=[*tests.data.SUPPORTED_KEYS.keys(), "singleton"], |
|
274 | 278 |
) |
275 | 279 |
SH_EXPORT_LINES = pytest.mark.parametrize( |
276 | 280 |
["line", "env_name", "value"], |
... | ... |
@@ -366,7 +370,7 @@ class Parametrize(types.SimpleNamespace): |
366 | 370 |
b"".join([ |
367 | 371 |
b"\x0d", |
368 | 372 |
ssh_agent.SSHAgentClient.string( |
369 |
- tests.ALL_KEYS["rsa"].public_key_data |
|
373 |
+ tests.data.ALL_KEYS["rsa"].public_key_data |
|
370 | 374 |
), |
371 | 375 |
ssh_agent.SSHAgentClient.string(vault.Vault.UUID), |
372 | 376 |
b"\x00\x00\x00\x02", |
... | ... |
@@ -379,7 +383,7 @@ class Parametrize(types.SimpleNamespace): |
379 | 383 |
b"".join([ |
380 | 384 |
b"\x0d", |
381 | 385 |
ssh_agent.SSHAgentClient.string( |
382 |
- tests.ALL_KEYS["ed25519"].public_key_data |
|
386 |
+ tests.data.ALL_KEYS["ed25519"].public_key_data |
|
383 | 387 |
), |
384 | 388 |
b"\x00\x00\x00\x08\x00\x01\x02\x03\x04\x05\x06\x07", |
385 | 389 |
b"\x00\x00\x00\x00", |
... | ... |
@@ -392,7 +396,7 @@ class Parametrize(types.SimpleNamespace): |
392 | 396 |
b"".join([ |
393 | 397 |
b"\x0d", |
394 | 398 |
ssh_agent.SSHAgentClient.string( |
395 |
- tests.ALL_KEYS["dsa1024"].public_key_data |
|
399 |
+ tests.data.ALL_KEYS["dsa1024"].public_key_data |
|
396 | 400 |
), |
397 | 401 |
ssh_agent.SSHAgentClient.string(vault.Vault.UUID), |
398 | 402 |
b"\x00\x00\x00\x00", |
... | ... |
@@ -415,8 +419,8 @@ class Parametrize(types.SimpleNamespace): |
415 | 419 |
) |
416 | 420 |
PUBLIC_KEY_DATA = pytest.mark.parametrize( |
417 | 421 |
"public_key_struct", |
418 |
- list(tests.SUPPORTED_KEYS.values()), |
|
419 |
- ids=list(tests.SUPPORTED_KEYS.keys()), |
|
422 |
+ list(tests.data.SUPPORTED_KEYS.values()), |
|
423 |
+ ids=list(tests.data.SUPPORTED_KEYS.keys()), |
|
420 | 424 |
) |
421 | 425 |
REQUEST_ERROR_RESPONSES = pytest.mark.parametrize( |
422 | 426 |
["request_code", "response_code", "exc_type", "exc_pattern"], |
... | ... |
@@ -484,13 +488,13 @@ class Parametrize(types.SimpleNamespace): |
484 | 488 |
) |
485 | 489 |
SUPPORTED_SSH_TEST_KEYS = pytest.mark.parametrize( |
486 | 490 |
["ssh_test_key_type", "ssh_test_key"], |
487 |
- list(tests.SUPPORTED_KEYS.items()), |
|
488 |
- ids=tests.SUPPORTED_KEYS.keys(), |
|
491 |
+ list(tests.data.SUPPORTED_KEYS.items()), |
|
492 |
+ ids=tests.data.SUPPORTED_KEYS.keys(), |
|
489 | 493 |
) |
490 | 494 |
UNSUITABLE_SSH_TEST_KEYS = pytest.mark.parametrize( |
491 | 495 |
["ssh_test_key_type", "ssh_test_key"], |
492 |
- list(tests.UNSUITABLE_KEYS.items()), |
|
493 |
- ids=tests.UNSUITABLE_KEYS.keys(), |
|
496 |
+ list(tests.data.UNSUITABLE_KEYS.items()), |
|
497 |
+ ids=tests.data.UNSUITABLE_KEYS.keys(), |
|
494 | 498 |
) |
495 | 499 |
RESOLVE_CHAINS = pytest.mark.parametrize( |
496 | 500 |
["terminal", "chain"], |
... | ... |
@@ -513,10 +517,10 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
513 | 517 |
with contextlib.ExitStack() as stack: |
514 | 518 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
515 | 519 |
monkeypatch.setenv( |
516 |
- "SSH_AUTH_SOCK", tests.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
520 |
+ "SSH_AUTH_SOCK", tests.machinery.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
517 | 521 |
) |
518 | 522 |
agent = stack.enter_context( |
519 |
- tests.StubbedSSHAgentSocketWithAddress() |
|
523 |
+ tests.machinery.StubbedSSHAgentSocketWithAddress() |
|
520 | 524 |
) |
521 | 525 |
assert "query" not in agent.enabled_extensions |
522 | 526 |
query_request = ( |
... | ... |
@@ -541,10 +545,10 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
541 | 545 |
with contextlib.ExitStack() as stack: |
542 | 546 |
monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
543 | 547 |
monkeypatch.setenv( |
544 |
- "SSH_AUTH_SOCK", tests.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
548 |
+ "SSH_AUTH_SOCK", tests.machinery.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
545 | 549 |
) |
546 | 550 |
agent = stack.enter_context( |
547 |
- tests.StubbedSSHAgentSocketWithAddressAndDeterministicDSA() |
|
551 |
+ tests.machinery.StubbedSSHAgentSocketWithAddressAndDeterministicDSA() |
|
548 | 552 |
) |
549 | 553 |
assert "query" in agent.enabled_extensions |
550 | 554 |
query_request = ( |
... | ... |
@@ -574,7 +578,7 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
574 | 578 |
def test_101_request_identities(self) -> None: |
575 | 579 |
"""The agent implements a known list of identities.""" |
576 | 580 |
unstring_prefix = ssh_agent.SSHAgentClient.unstring_prefix |
577 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
581 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
578 | 582 |
query_request = ( |
579 | 583 |
# SSH string header |
580 | 584 |
b"\x00\x00\x00\x01" |
... | ... |
@@ -597,7 +601,7 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
597 | 601 |
_comment, message = unstring_prefix(message) |
598 | 602 |
assert key |
599 | 603 |
assert key in { |
600 |
- k.public_key_data for k in tests.ALL_KEYS.values() |
|
604 |
+ k.public_key_data for k in tests.data.ALL_KEYS.values() |
|
601 | 605 |
} |
602 | 606 |
assert not message |
603 | 607 |
|
... | ... |
@@ -605,11 +609,11 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
605 | 609 |
def test_102_sign( |
606 | 610 |
self, |
607 | 611 |
ssh_test_key_type: str, |
608 |
- ssh_test_key: tests.SSHTestKey, |
|
612 |
+ ssh_test_key: tests.data.SSHTestKey, |
|
609 | 613 |
) -> None: |
610 | 614 |
"""The agent signs known key/message pairs.""" |
611 | 615 |
del ssh_test_key_type |
612 |
- spec = tests.SSHTestKeyDeterministicSignatureClass.SPEC |
|
616 |
+ spec = tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
613 | 617 |
assert ssh_test_key.expected_signatures[spec].signature is not None |
614 | 618 |
string = ssh_agent.SSHAgentClient.string |
615 | 619 |
query_request = string( |
... | ... |
@@ -628,21 +632,21 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
628 | 632 |
# expected payload: the binary signature as recorded in the test key data structure |
629 | 633 |
+ string(ssh_test_key.expected_signatures[spec].signature) |
630 | 634 |
) |
631 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
635 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
632 | 636 |
agent.sendall(query_request) |
633 | 637 |
assert agent.recv(1000) == query_response |
634 | 638 |
|
635 | 639 |
def test_120_close_multiple(self) -> None: |
636 | 640 |
"""The agent can be closed repeatedly.""" |
637 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
641 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
638 | 642 |
pass |
639 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
643 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
640 | 644 |
pass |
641 | 645 |
del agent |
642 | 646 |
|
643 | 647 |
def test_121_closed_agents_cannot_be_interacted_with(self) -> None: |
644 | 648 |
"""The agent can be closed repeatedly.""" |
645 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
649 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
646 | 650 |
pass |
647 | 651 |
query_request = ( |
648 | 652 |
# SSH string header |
... | ... |
@@ -655,18 +659,18 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
655 | 659 |
query_response = b"" |
656 | 660 |
with pytest.raises( |
657 | 661 |
ValueError, |
658 |
- match=re.escape(tests.StubbedSSHAgentSocket._SOCKET_IS_CLOSED), |
|
662 |
+ match=re.escape(tests.machinery.StubbedSSHAgentSocket._SOCKET_IS_CLOSED), |
|
659 | 663 |
): |
660 | 664 |
agent.sendall(query_request) |
661 | 665 |
assert agent.recv(100) == query_response |
662 | 666 |
|
663 | 667 |
def test_122_no_recv_without_sendall(self) -> None: |
664 | 668 |
"""The agent requires a message before sending a response.""" |
665 |
- with tests.StubbedSSHAgentSocket() as agent: # noqa: SIM117 |
|
669 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: # noqa: SIM117 |
|
666 | 670 |
with pytest.raises( |
667 | 671 |
AssertionError, |
668 | 672 |
match=re.escape( |
669 |
- tests.StubbedSSHAgentSocket._PROTOCOL_VIOLATION |
|
673 |
+ tests.machinery.StubbedSSHAgentSocket._PROTOCOL_VIOLATION |
|
670 | 674 |
), |
671 | 675 |
): |
672 | 676 |
agent.recv(100) |
... | ... |
@@ -683,7 +687,7 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
683 | 687 |
# response code: SSH_AGENT_FAILURE |
684 | 688 |
b"\x05" |
685 | 689 |
) |
686 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
690 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
687 | 691 |
agent.sendall(message) |
688 | 692 |
assert agent.recv(100) == query_response |
689 | 693 |
|
... | ... |
@@ -699,7 +703,7 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
699 | 703 |
# response code: SSH_AGENT_FAILURE |
700 | 704 |
b"\x05" |
701 | 705 |
) |
702 |
- with tests.StubbedSSHAgentSocket() as agent: |
|
706 |
+ with tests.machinery.StubbedSSHAgentSocket() as agent: |
|
703 | 707 |
agent.sendall(message) |
704 | 708 |
assert agent.recv(100) == query_response |
705 | 709 |
|
... | ... |
@@ -721,7 +725,7 @@ class TestTestingMachineryStubbedSSHAgentSocket: |
721 | 725 |
stack.enter_context( |
722 | 726 |
pytest.raises(exception, match=re.escape(match)) |
723 | 727 |
) |
724 |
- tests.StubbedSSHAgentSocketWithAddress() |
|
728 |
+ tests.machinery.StubbedSSHAgentSocketWithAddress() |
|
725 | 729 |
|
726 | 730 |
|
727 | 731 |
class TestStaticFunctionality: |
... | ... |
@@ -777,7 +781,7 @@ class TestStaticFunctionality: |
777 | 781 |
@Parametrize.PUBLIC_KEY_DATA |
778 | 782 |
def test_100_key_decoding( |
779 | 783 |
self, |
780 |
- public_key_struct: tests.SSHTestKey, |
|
784 |
+ public_key_struct: tests.data.SSHTestKey, |
|
781 | 785 |
) -> None: |
782 | 786 |
"""The [`tests.ALL_KEYS`][] public key data looks sane.""" |
783 | 787 |
keydata = base64.b64decode( |
... | ... |
@@ -793,10 +797,10 @@ class TestStaticFunctionality: |
793 | 797 |
) -> None: |
794 | 798 |
"""[`tests.parse_sh_export_line`][] works.""" |
795 | 799 |
if value is not None: |
796 |
- assert tests.parse_sh_export_line(line, env_name=env_name) == value |
|
800 |
+ assert tests.data.callables.parse_sh_export_line(line, env_name=env_name) == value |
|
797 | 801 |
else: |
798 | 802 |
with pytest.raises(ValueError, match="Cannot parse sh line:"): |
799 |
- tests.parse_sh_export_line(line, env_name=env_name) |
|
803 |
+ tests.data.callables.parse_sh_export_line(line, env_name=env_name) |
|
800 | 804 |
|
801 | 805 |
def test_200_constructor_posix_no_ssh_auth_sock( |
802 | 806 |
self, |
... | ... |
@@ -1041,14 +1045,14 @@ class TestStaticFunctionality: |
1041 | 1045 |
"""Finding all SSH agent socket providers works.""" |
1042 | 1046 |
resolve = socketprovider.SocketProvider.resolve |
1043 | 1047 |
old_registry = socketprovider.SocketProvider.registry |
1044 |
- with tests.faked_entry_point_list( |
|
1048 |
+ with tests.machinery.pytest.faked_entry_point_list( |
|
1045 | 1049 |
additional_entry_points, remove_conflicting_entries=False |
1046 | 1050 |
) as names: |
1047 | 1051 |
socketprovider.SocketProvider._find_all_ssh_agent_socket_providers() |
1048 | 1052 |
for name in names: |
1049 | 1053 |
assert name in socketprovider.SocketProvider.registry |
1050 | 1054 |
assert resolve(name) in { |
1051 |
- tests.provider_entry_provider, |
|
1055 |
+ tests.data.callables.provider_entry_provider, |
|
1052 | 1056 |
*old_registry.values(), |
1053 | 1057 |
} |
1054 | 1058 |
|
... | ... |
@@ -1060,7 +1064,7 @@ class TestStaticFunctionality: |
1060 | 1064 |
"""Finding faulty SSH agent socket providers raises errors.""" |
1061 | 1065 |
with contextlib.ExitStack() as stack: |
1062 | 1066 |
stack.enter_context( |
1063 |
- tests.faked_entry_point_list( |
|
1067 |
+ tests.machinery.pytest.faked_entry_point_list( |
|
1064 | 1068 |
additional_entry_points, remove_conflicting_entries=False |
1065 | 1069 |
) |
1066 | 1070 |
) |
... | ... |
@@ -1231,7 +1235,7 @@ class TestAgentInteraction: |
1231 | 1235 |
self, |
1232 | 1236 |
ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient, |
1233 | 1237 |
ssh_test_key_type: str, |
1234 |
- ssh_test_key: tests.SSHTestKey, |
|
1238 |
+ ssh_test_key: tests.data.SSHTestKey, |
|
1235 | 1239 |
) -> None: |
1236 | 1240 |
"""Signing data with specific SSH keys works. |
1237 | 1241 |
|
... | ... |
@@ -1244,11 +1248,11 @@ class TestAgentInteraction: |
1244 | 1248 |
key_comment_pairs = {bytes(k): bytes(c) for k, c in client.list_keys()} |
1245 | 1249 |
public_key_data = ssh_test_key.public_key_data |
1246 | 1250 |
assert ( |
1247 |
- tests.SSHTestKeyDeterministicSignatureClass.SPEC |
|
1251 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
1248 | 1252 |
in ssh_test_key.expected_signatures |
1249 | 1253 |
) |
1250 | 1254 |
sig = ssh_test_key.expected_signatures[ |
1251 |
- tests.SSHTestKeyDeterministicSignatureClass.SPEC |
|
1255 |
+ tests.data.SSHTestKeyDeterministicSignatureClass.SPEC |
|
1252 | 1256 |
] |
1253 | 1257 |
expected_signature = sig.signature |
1254 | 1258 |
derived_passphrase = sig.derived_passphrase |
... | ... |
@@ -1276,7 +1280,7 @@ class TestAgentInteraction: |
1276 | 1280 |
self, |
1277 | 1281 |
ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient, |
1278 | 1282 |
ssh_test_key_type: str, |
1279 |
- ssh_test_key: tests.SSHTestKey, |
|
1283 |
+ ssh_test_key: tests.data.SSHTestKey, |
|
1280 | 1284 |
) -> None: |
1281 | 1285 |
"""Using an unsuitable key with [`vault.Vault`][] fails. |
1282 | 1286 |
|
... | ... |
@@ -1320,10 +1324,10 @@ class TestAgentInteraction: |
1320 | 1324 |
|
1321 | 1325 |
def key_is_suitable(key: bytes) -> bool: |
1322 | 1326 |
"""Stub out [`vault.Vault.key_is_suitable`][].""" |
1323 |
- always = {v.public_key_data for v in tests.SUPPORTED_KEYS.values()} |
|
1327 |
+ always = {v.public_key_data for v in tests.data.SUPPORTED_KEYS.values()} |
|
1324 | 1328 |
dsa = { |
1325 | 1329 |
v.public_key_data |
1326 |
- for k, v in tests.UNSUITABLE_KEYS.items() |
|
1330 |
+ for k, v in tests.data.UNSUITABLE_KEYS.items() |
|
1327 | 1331 |
if k.startswith(("dsa", "ecdsa")) |
1328 | 1332 |
} |
1329 | 1333 |
return key in always or ( |
... | ... |
@@ -1339,22 +1343,22 @@ class TestAgentInteraction: |
1339 | 1343 |
monkeypatch.setattr( |
1340 | 1344 |
ssh_agent.SSHAgentClient, |
1341 | 1345 |
"list_keys", |
1342 |
- tests.list_keys_singleton, |
|
1346 |
+ tests.data.callables.list_keys_singleton, |
|
1343 | 1347 |
) |
1344 | 1348 |
keys = [ |
1345 | 1349 |
pair.key |
1346 |
- for pair in tests.list_keys_singleton() |
|
1350 |
+ for pair in tests.data.callables.list_keys_singleton() |
|
1347 | 1351 |
if key_is_suitable(pair.key) |
1348 | 1352 |
] |
1349 | 1353 |
index = "1" |
1350 | 1354 |
text = "Use this key? yes\n" |
1351 | 1355 |
else: |
1352 | 1356 |
monkeypatch.setattr( |
1353 |
- ssh_agent.SSHAgentClient, "list_keys", tests.list_keys |
|
1357 |
+ ssh_agent.SSHAgentClient, "list_keys", tests.data.callables.list_keys |
|
1354 | 1358 |
) |
1355 | 1359 |
keys = [ |
1356 | 1360 |
pair.key |
1357 |
- for pair in tests.list_keys() |
|
1361 |
+ for pair in tests.data.callables.list_keys() |
|
1358 | 1362 |
if key_is_suitable(pair.key) |
1359 | 1363 |
] |
1360 | 1364 |
index = str(1 + keys.index(key)) |
... | ... |
@@ -1370,7 +1374,7 @@ class TestAgentInteraction: |
1370 | 1374 |
|
1371 | 1375 |
# TODO(the-13th-letter): (Continued from above.) Update input |
1372 | 1376 |
# data to use `index`/`input` directly and unconditionally. |
1373 |
- runner = tests.CliRunner(mix_stderr=True) |
|
1377 |
+ runner = tests.machinery.CliRunner(mix_stderr=True) |
|
1374 | 1378 |
result = runner.invoke( |
1375 | 1379 |
driver, |
1376 | 1380 |
[], |
... | ... |
@@ -1382,7 +1386,7 @@ class TestAgentInteraction: |
1382 | 1386 |
|
1383 | 1387 |
def test_300_constructor_bad_running_agent( |
1384 | 1388 |
self, |
1385 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1389 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1386 | 1390 |
) -> None: |
1387 | 1391 |
"""Fail if the agent address is invalid.""" |
1388 | 1392 |
with pytest.MonkeyPatch.context() as monkeypatch: |
... | ... |
@@ -1426,7 +1430,7 @@ class TestAgentInteraction: |
1426 | 1430 |
|
1427 | 1431 |
def test_303_explicit_socket( |
1428 | 1432 |
self, |
1429 |
- spawn_ssh_agent: tests.SpawnedSSHAgentInfo, |
|
1433 |
+ spawn_ssh_agent: tests.data.SpawnedSSHAgentInfo, |
|
1430 | 1434 |
) -> None: |
1431 | 1435 |
conn = spawn_ssh_agent.client._connection |
1432 | 1436 |
ssh_agent.SSHAgentClient(socket=conn) |
... | ... |
@@ -1434,7 +1438,7 @@ class TestAgentInteraction: |
1434 | 1438 |
@Parametrize.TRUNCATED_AGENT_RESPONSES |
1435 | 1439 |
def test_310_truncated_server_response( |
1436 | 1440 |
self, |
1437 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1441 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1438 | 1442 |
response: bytes, |
1439 | 1443 |
) -> None: |
1440 | 1444 |
"""Fail on truncated responses from the SSH agent.""" |
... | ... |
@@ -1458,7 +1462,7 @@ class TestAgentInteraction: |
1458 | 1462 |
@Parametrize.LIST_KEYS_ERROR_RESPONSES |
1459 | 1463 |
def test_320_list_keys_error_responses( |
1460 | 1464 |
self, |
1461 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1465 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1462 | 1466 |
response_code: _types.SSH_AGENT, |
1463 | 1467 |
response: bytes | bytearray, |
1464 | 1468 |
exc_type: type[Exception], |
... | ... |
@@ -1518,7 +1522,7 @@ class TestAgentInteraction: |
1518 | 1522 |
@Parametrize.SIGN_ERROR_RESPONSES |
1519 | 1523 |
def test_330_sign_error_responses( |
1520 | 1524 |
self, |
1521 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1525 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1522 | 1526 |
key: bytes | bytearray, |
1523 | 1527 |
check: bool, |
1524 | 1528 |
response_code: _types.SSH_AGENT, |
... | ... |
@@ -1578,7 +1582,7 @@ class TestAgentInteraction: |
1578 | 1582 |
com = b"no comment" |
1579 | 1583 |
loaded_keys = [ |
1580 | 1584 |
Pair(v.public_key_data, com).toreadonly() |
1581 |
- for v in tests.SUPPORTED_KEYS.values() |
|
1585 |
+ for v in tests.data.SUPPORTED_KEYS.values() |
|
1582 | 1586 |
] |
1583 | 1587 |
monkeypatch.setattr(client, "list_keys", lambda: loaded_keys) |
1584 | 1588 |
with pytest.raises(exc_type, match=exc_pattern): |
... | ... |
@@ -1587,7 +1591,7 @@ class TestAgentInteraction: |
1587 | 1591 |
@Parametrize.REQUEST_ERROR_RESPONSES |
1588 | 1592 |
def test_340_request_error_responses( |
1589 | 1593 |
self, |
1590 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1594 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1591 | 1595 |
request_code: _types.SSH_AGENTC, |
1592 | 1596 |
response_code: _types.SSH_AGENT, |
1593 | 1597 |
exc_type: type[Exception], |
... | ... |
@@ -1616,7 +1620,7 @@ class TestAgentInteraction: |
1616 | 1620 |
def test_350_query_extensions_malformed_responses( |
1617 | 1621 |
self, |
1618 | 1622 |
monkeypatch: pytest.MonkeyPatch, |
1619 |
- running_ssh_agent: tests.RunningSSHAgentInfo, |
|
1623 |
+ running_ssh_agent: tests.data.RunningSSHAgentInfo, |
|
1620 | 1624 |
response_data: bytes, |
1621 | 1625 |
) -> None: |
1622 | 1626 |
"""Fail on malformed responses while querying extensions.""" |
... | ... |
@@ -13,7 +13,9 @@ import pytest |
13 | 13 |
from hypothesis import strategies |
14 | 14 |
from typing_extensions import Any |
15 | 15 |
|
16 |
-import tests |
|
16 |
+import tests.data |
|
17 |
+import tests.data.callables |
|
18 |
+import tests.machinery.hypothesis |
|
17 | 19 |
from derivepassphrase import _types |
18 | 20 |
|
19 | 21 |
|
... | ... |
@@ -72,13 +74,13 @@ class Parametrize(types.SimpleNamespace): |
72 | 74 |
"test_config", |
73 | 75 |
[ |
74 | 76 |
conf |
75 |
- for conf in tests.TEST_CONFIGS |
|
77 |
+ for conf in tests.data.TEST_CONFIGS |
|
76 | 78 |
if conf.validation_settings in {None, (True,)} |
77 | 79 |
], |
78 |
- ids=tests._test_config_ids, |
|
80 |
+ ids=tests.data._test_config_ids, |
|
79 | 81 |
) |
80 | 82 |
VAULT_TEST_CONFIGS = pytest.mark.parametrize( |
81 |
- "test_config", tests.TEST_CONFIGS, ids=tests._test_config_ids |
|
83 |
+ "test_config", tests.data.TEST_CONFIGS, ids=tests.data._test_config_ids |
|
82 | 84 |
) |
83 | 85 |
|
84 | 86 |
|
... | ... |
@@ -102,7 +104,7 @@ def test_100_js_truthiness(value: Any) -> None: |
102 | 104 |
|
103 | 105 |
|
104 | 106 |
@Parametrize.VALID_VAULT_TEST_CONFIGS |
105 |
-def test_200_is_vault_config(test_config: tests.VaultTestConfig) -> None: |
|
107 |
+def test_200_is_vault_config(test_config: tests.data.VaultTestConfig) -> None: |
|
106 | 108 |
"""Is this vault configuration recognized as valid/invalid? |
107 | 109 |
|
108 | 110 |
Check all test configurations that do not need custom validation |
... | ... |
@@ -123,16 +125,16 @@ def test_200_is_vault_config(test_config: tests.VaultTestConfig) -> None: |
123 | 125 |
|
124 | 126 |
|
125 | 127 |
@hypothesis.given( |
126 |
- test_config=tests.smudged_vault_test_config( |
|
128 |
+ test_config=tests.machinery.hypothesis.smudged_vault_test_config( |
|
127 | 129 |
config=strategies.sampled_from([ |
128 | 130 |
conf |
129 |
- for conf in tests.TEST_CONFIGS |
|
130 |
- if tests.is_valid_test_config(conf) |
|
131 |
+ for conf in tests.data.TEST_CONFIGS |
|
132 |
+ if tests.data.is_valid_test_config(conf) |
|
131 | 133 |
]) |
132 | 134 |
) |
133 | 135 |
) |
134 | 136 |
def test_200a_is_vault_config_smudged( |
135 |
- test_config: tests.VaultTestConfig, |
|
137 |
+ test_config: tests.data.VaultTestConfig, |
|
136 | 138 |
) -> None: |
137 | 139 |
"""Is this vault configuration recognized as valid/invalid? |
138 | 140 |
|
... | ... |
@@ -157,7 +159,7 @@ def test_200a_is_vault_config_smudged( |
157 | 159 |
|
158 | 160 |
|
159 | 161 |
@Parametrize.VAULT_TEST_CONFIGS |
160 |
-def test_400_validate_vault_config(test_config: tests.VaultTestConfig) -> None: |
|
162 |
+def test_400_validate_vault_config(test_config: tests.data.VaultTestConfig) -> None: |
|
161 | 163 |
"""Validate this vault configuration. |
162 | 164 |
|
163 | 165 |
Check all test configurations, including those with non-standard |
... | ... |
@@ -188,16 +190,16 @@ def test_400_validate_vault_config(test_config: tests.VaultTestConfig) -> None: |
188 | 190 |
|
189 | 191 |
|
190 | 192 |
@hypothesis.given( |
191 |
- test_config=tests.smudged_vault_test_config( |
|
193 |
+ test_config=tests.machinery.hypothesis.smudged_vault_test_config( |
|
192 | 194 |
config=strategies.sampled_from([ |
193 | 195 |
conf |
194 |
- for conf in tests.TEST_CONFIGS |
|
195 |
- if tests.is_smudgable_vault_test_config(conf) |
|
196 |
+ for conf in tests.data.TEST_CONFIGS |
|
197 |
+ if tests.data.is_smudgable_vault_test_config(conf) |
|
196 | 198 |
]) |
197 | 199 |
) |
198 | 200 |
) |
199 | 201 |
def test_400a_validate_vault_config_smudged( |
200 |
- test_config: tests.VaultTestConfig, |
|
202 |
+ test_config: tests.data.VaultTestConfig, |
|
201 | 203 |
) -> None: |
202 | 204 |
"""Validate this vault configuration. |
203 | 205 |
|
... | ... |
@@ -17,7 +17,7 @@ import pytest |
17 | 17 |
from hypothesis import strategies |
18 | 18 |
from typing_extensions import TypeVar |
19 | 19 |
|
20 |
-import tests |
|
20 |
+import tests.machinery.hypothesis |
|
21 | 21 |
from derivepassphrase import vault |
22 | 22 |
|
23 | 23 |
if TYPE_CHECKING: |
... | ... |
@@ -503,7 +503,7 @@ class TestVault: |
503 | 503 |
min_size=1, |
504 | 504 |
max_size=32, |
505 | 505 |
), |
506 |
- config=tests.vault_full_service_config(), |
|
506 |
+ config=tests.machinery.hypothesis.vault_full_service_config(), |
|
507 | 507 |
services=strategies.lists( |
508 | 508 |
strategies.binary(min_size=1, max_size=32), |
509 | 509 |
min_size=2, |
... | ... |
@@ -629,7 +629,7 @@ class TestVault: |
629 | 629 |
phrase=strategies.one_of( |
630 | 630 |
strategies.binary(min_size=1), strategies.text(min_size=1) |
631 | 631 |
), |
632 |
- config=tests.vault_full_service_config(), |
|
632 |
+ config=tests.machinery.hypothesis.vault_full_service_config(), |
|
633 | 633 |
service=strategies.text(min_size=1), |
634 | 634 |
) |
635 | 635 |
@hypothesis.example( |
636 | 636 |