Marco Ricci commited on 2024-05-21 00:40:13
Zeige 7 geänderte Dateien mit 721 Einfügungen und 13 Löschungen.
doctests which are not pedagogical, but unit test-y in nature, are better off rewritten as proper unit tests.
... | ... |
@@ -187,6 +187,15 @@ class Vault: |
187 | 187 |
If given, override the passphrase given during |
188 | 188 |
construction. |
189 | 189 |
|
190 |
+ Examples: |
|
191 |
+ >>> phrase = b'She cells C shells bye the sea shoars' |
|
192 |
+ >>> # Using default options in constructor. |
|
193 |
+ >>> Vault(phrase=phrase).generate(b'google') |
|
194 |
+ b': 4TVH#5:aZl8LueOT\\{' |
|
195 |
+ >>> # Also possible: |
|
196 |
+ >>> Vault().generate(b'google', phrase=phrase) |
|
197 |
+ b': 4TVH#5:aZl8LueOT\\{' |
|
198 |
+ |
|
190 | 199 |
""" |
191 | 200 |
entropy_bound = self._entropy_upper_bound() |
192 | 201 |
# Use a safety factor, because a sequin will potentially throw |
... | ... |
@@ -172,20 +172,14 @@ class Sequin: |
172 | 172 |
Examples: |
173 | 173 |
>>> Sequin._big_endian_number([1, 2, 3, 4, 5, 6, 7, 8], base=10) |
174 | 174 |
12345678 |
175 |
+ >>> Sequin._big_endian_number([1, 2, 3, 4, 5, 6, 7, 8], base=100) |
|
176 |
+ 102030405060708 |
|
175 | 177 |
>>> Sequin._big_endian_number([0, 0, 0, 0, 1, 4, 9, 7], base=10) |
176 | 178 |
1497 |
177 | 179 |
>>> Sequin._big_endian_number([1, 0, 0, 1, 0, 0, 0, 0], base=2) |
178 | 180 |
144 |
179 | 181 |
>>> Sequin._big_endian_number([1, 7, 5, 5], base=8) == 0o1755 |
180 | 182 |
True |
181 |
- >>> Sequin._big_endian_number([-1], base=3) # doctest: +ELLIPSIS |
|
182 |
- Traceback (most recent call last): |
|
183 |
- ... |
|
184 |
- ValueError: ... |
|
185 |
- >>> Sequin._big_endian_number([0], base=1) # doctest: +ELLIPSIS |
|
186 |
- Traceback (most recent call last): |
|
187 |
- ... |
|
188 |
- ValueError: ... |
|
189 | 183 |
|
190 | 184 |
""" |
191 | 185 |
if base < 2: |
... | ... |
@@ -226,6 +220,34 @@ class Sequin: |
226 | 220 |
SequinExhaustedException: |
227 | 221 |
The sequin is exhausted. |
228 | 222 |
|
223 |
+ Examples: |
|
224 |
+ >>> seq = Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
225 |
+ ... is_bitstring=True) |
|
226 |
+ >>> seq2 = Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
227 |
+ ... is_bitstring=True) |
|
228 |
+ >>> seq.generate(5) |
|
229 |
+ 3 |
|
230 |
+ >>> seq.generate(5) |
|
231 |
+ 3 |
|
232 |
+ >>> seq.generate(5) |
|
233 |
+ 1 |
|
234 |
+ >>> seq.generate(5) # doctest: +IGNORE_EXCEPTION_DETAIL |
|
235 |
+ Traceback (most recent call last): |
|
236 |
+ ... |
|
237 |
+ SequinExhaustedException: Sequin is exhausted |
|
238 |
+ |
|
239 |
+ Using `n = 1` does not actually consume input bits: |
|
240 |
+ |
|
241 |
+ >>> seq2.generate(1) |
|
242 |
+ 0 |
|
243 |
+ |
|
244 |
+ But it still won't work on exhausted sequins: |
|
245 |
+ |
|
246 |
+ >>> seq.generate(1) # doctest: +IGNORE_EXCEPTION_DETAIL |
|
247 |
+ Traceback (most recent call last): |
|
248 |
+ ... |
|
249 |
+ SequinExhaustedException: Sequin is exhausted |
|
250 |
+ |
|
229 | 251 |
""" |
230 | 252 |
if 2 not in self.bases: |
231 | 253 |
raise SequinExhaustedException('Sequin is exhausted') |
... | ... |
@@ -265,6 +287,35 @@ class Sequin: |
265 | 287 |
ValueError: |
266 | 288 |
The range is empty. |
267 | 289 |
|
290 |
+ Examples: |
|
291 |
+ >>> seq = Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
292 |
+ ... is_bitstring=True) |
|
293 |
+ >>> seq2 = Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
294 |
+ ... is_bitstring=True) |
|
295 |
+ >>> seq._generate_inner(5) |
|
296 |
+ 3 |
|
297 |
+ >>> seq._generate_inner(5) |
|
298 |
+ 3 |
|
299 |
+ >>> seq._generate_inner(5) |
|
300 |
+ 1 |
|
301 |
+ >>> seq._generate_inner(5) # error condition: sequin exhausted |
|
302 |
+ 5 |
|
303 |
+ |
|
304 |
+ Using `n = 1` does not actually consume input bits, and |
|
305 |
+ always works, regardless of sequin exhaustion: |
|
306 |
+ |
|
307 |
+ >>> seq2._generate_inner(1) |
|
308 |
+ 0 |
|
309 |
+ >>> seq._generate_inner(1) |
|
310 |
+ 0 |
|
311 |
+ |
|
312 |
+ Using an unsuitable range will raise: |
|
313 |
+ |
|
314 |
+ >>> seq2._generate_inner(0) # doctest: +IGNORE_EXCEPTION_DETAIL |
|
315 |
+ Traceback (most recent call last): |
|
316 |
+ ... |
|
317 |
+ ValueError: invalid target range |
|
318 |
+ |
|
268 | 319 |
""" |
269 | 320 |
if n < 1: |
270 | 321 |
raise ValueError('invalid target range') |
... | ... |
@@ -99,13 +99,42 @@ class SSHAgentClient: |
99 | 99 |
) |
100 | 100 |
|
101 | 101 |
@staticmethod |
102 |
- def uint32(num, /) -> bytes: |
|
103 |
- """Format the number as a `uint32`, as per the agent protocol.""" |
|
102 |
+ def uint32(num: int, /) -> bytes: |
|
103 |
+ r"""Format the number as a `uint32`, as per the agent protocol. |
|
104 |
+ |
|
105 |
+ Args: |
|
106 |
+ num: A number. |
|
107 |
+ |
|
108 |
+ Returns: |
|
109 |
+ The number in SSH agent wire protocol format, i.e. as |
|
110 |
+ a 32-bit big endian number. |
|
111 |
+ |
|
112 |
+ Raises: |
|
113 |
+ OverflowError: |
|
114 |
+ As per [`int.to_bytes`][]. |
|
115 |
+ |
|
116 |
+ Examples: |
|
117 |
+ >>> SSHAgentClient.uint32(16777216) |
|
118 |
+ b'\x01\x00\x00\x00' |
|
119 |
+ |
|
120 |
+ """ |
|
104 | 121 |
return int.to_bytes(num, 4, 'big', signed=False) |
105 | 122 |
|
106 | 123 |
@classmethod |
107 | 124 |
def string(cls, payload: bytes | bytearray, /) -> bytes | bytearray: |
108 |
- """Format the payload as an SSH string, as per the agent protocol.""" |
|
125 |
+ r"""Format the payload as an SSH string, as per the agent protocol. |
|
126 |
+ |
|
127 |
+ Args: |
|
128 |
+ payload: A byte string. |
|
129 |
+ |
|
130 |
+ Returns: |
|
131 |
+ The payload, framed in the SSH agent wire protocol format. |
|
132 |
+ |
|
133 |
+ Examples: |
|
134 |
+ >>> bytes(SSHAgentClient.string(b'ssh-rsa')) |
|
135 |
+ b'\x00\x00\x00\x07ssh-rsa' |
|
136 |
+ |
|
137 |
+ """ |
|
109 | 138 |
try: |
110 | 139 |
ret = bytearray() |
111 | 140 |
ret.extend(cls.uint32(len(payload))) |
... | ... |
@@ -116,7 +145,25 @@ class SSHAgentClient: |
116 | 145 |
|
117 | 146 |
@classmethod |
118 | 147 |
def unstring(cls, bytestring: bytes | bytearray, /) -> bytes | bytearray: |
119 |
- """Unpack an SSH string.""" |
|
148 |
+ r"""Unpack an SSH string. |
|
149 |
+ |
|
150 |
+ Args: |
|
151 |
+ bytestring: A framed byte string. |
|
152 |
+ |
|
153 |
+ Returns: |
|
154 |
+ The unframed byte string, i.e., the payload. |
|
155 |
+ |
|
156 |
+ Raises: |
|
157 |
+ ValueError: |
|
158 |
+ The bytestring is not an SSH string. |
|
159 |
+ |
|
160 |
+ Examples: |
|
161 |
+ >>> bytes(SSHAgentClient.unstring(b'\x00\x00\x00\x07ssh-rsa')) |
|
162 |
+ b'ssh-rsa' |
|
163 |
+ >>> bytes(SSHAgentClient.unstring(SSHAgentClient.string(b'ssh-ed25519'))) |
|
164 |
+ b'ssh-ed25519' |
|
165 |
+ |
|
166 |
+ """ |
|
120 | 167 |
n = len(bytestring) |
121 | 168 |
if n < 4: |
122 | 169 |
raise ValueError('malformed SSH byte string') |
... | ... |
@@ -0,0 +1,406 @@ |
1 |
+# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: MIT |
|
4 |
+ |
|
5 |
+"""Test OpenSSH key loading and signing.""" |
|
6 |
+ |
|
7 |
+import pytest |
|
8 |
+ |
|
9 |
+import derivepassphrase |
|
10 |
+import ssh_agent_client |
|
11 |
+ |
|
12 |
+import base64 |
|
13 |
+import os |
|
14 |
+import socket |
|
15 |
+import subprocess |
|
16 |
+ |
|
17 |
+SUPPORTED = { |
|
18 |
+ 'ed25519': { |
|
19 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
20 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW |
|
21 |
+QyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdgAAAKDweO7H8Hju |
|
22 |
+xwAAAAtzc2gtZWQyNTUxOQAAACCBeIFoJtYCSF8P/zJIb+TBMIncHGpFBgnpCQ/7whJpdg |
|
23 |
+AAAEAbM/A869nkWZbe2tp3Dm/L6gitvmpH/aRZt8sBII3ExYF4gWgm1gJIXw//Mkhv5MEw |
|
24 |
+idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC |
|
25 |
+-----END OPENSSH PRIVATE KEY----- |
|
26 |
+''', |
|
27 |
+ 'public_key': rb'''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIF4gWgm1gJIXw//Mkhv5MEwidwcakUGCekJD/vCEml2 test key without passphrase |
|
28 |
+''', |
|
29 |
+ 'public_key_data': bytes.fromhex(''' |
|
30 |
+ 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
31 |
+ 00 00 00 20 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f |
|
32 |
+ e4 c1 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76 |
|
33 |
+'''), |
|
34 |
+ 'expected_signature': bytes.fromhex(''' |
|
35 |
+ 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 |
|
36 |
+ 00 00 00 40 f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86 |
|
37 |
+ 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd 0d 08 1f ec |
|
38 |
+ f8 73 9b 8c 5f 55 39 16 7c 53 54 2c 1e 52 bb 30 ed 7f 89 e2 |
|
39 |
+ 2f 69 51 55 d8 9e a6 02 |
|
40 |
+ '''), |
|
41 |
+ }, |
|
42 |
+ # Currently only supported by PuTTY (which is deficient in other |
|
43 |
+ # niceties of the SSH agent and the agent's client). |
|
44 |
+ 'ed448': { |
|
45 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
46 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz |
|
47 |
+c2gtZWQ0NDgAAAA54vZy009Wu8wExjvEb3hqtLz1GO/+d5vmGUbErWQ4AUO9mYLT |
|
48 |
+zHJHc2m4s+yWzP29Cc3EcxizLG8AAAAA8BdhfCcXYXwnAAAACXNzaC1lZDQ0OAAA |
|
49 |
+ADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM |
|
50 |
+/b0JzcRzGLMsbwAAAAByM7GIMRvWJB3YD6SIpAF2uudX4ozZe0X917wPwiBrs373 |
|
51 |
+9TM1n94Nib6hrxGNmCk2iBQDe2KALPgA4vZy009Wu8wExjvEb3hqtLz1GO/+d5vm |
|
52 |
+GUbErWQ4AUO9mYLTzHJHc2m4s+yWzP29Cc3EcxizLG8AAAAAG3Rlc3Qga2V5IHdp |
|
53 |
+dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ== |
|
54 |
+-----END OPENSSH PRIVATE KEY----- |
|
55 |
+''', |
|
56 |
+ 'public_key': rb'''ssh-ed448 AAAACXNzaC1lZDQ0OAAAADni9nLTT1a7zATGO8RveGq0vPUY7/53m+YZRsStZDgBQ72ZgtPMckdzabiz7JbM/b0JzcRzGLMsbwA= test key without passphrase |
|
57 |
+''', |
|
58 |
+ 'public_key_data': bytes.fromhex(''' |
|
59 |
+ 00 00 00 09 73 73 68 2d 65 64 34 34 38 |
|
60 |
+ 00 00 00 39 e2 f6 72 d3 4f 56 bb cc 04 c6 3b c4 6f 78 6a b4 |
|
61 |
+ bc f5 18 ef fe 77 9b e6 19 46 c4 ad 64 38 01 43 bd 99 82 d3 |
|
62 |
+ cc 72 47 73 69 b8 b3 ec 96 cc fd bd 09 cd c4 73 18 b3 2c 6f |
|
63 |
+ 00 |
|
64 |
+ '''), |
|
65 |
+ |
|
66 |
+ 'expected_signature': ( |
|
67 |
+ b'\x00\x00\x00\tssh-ed448\x00\x00\x00r\x06\x86\xf4d\xa4\xa6\xba\xd9\xc3"\xc4' |
|
68 |
+ b'\x93I\x99\xfc\x11\xdeg\x97\x08\xf2\xd8\xb7<,\x13\xe7\xc5\x1c\x1e\x92' |
|
69 |
+ b'\xa6\x0e\xd8/m\x81\x03\x82\x00\xe3r\xe42mr\xd2m2\x84?\xcc\xa9\x1eW' |
|
70 |
+ b',\x00\x9a\xb3\x99\xdeE\xda\xce.\xd1\xdb\xe5\x89\xf35\xbe$X\x90' |
|
71 |
+ b'\xc6\xca\x04\xf0\xdb\x88\x80\xdb\xbdw|\x80 \x7f:Ha\xf6\x1f\xae\xa9^S{' |
|
72 |
+ b'\xe0\x9d\x93\x1e\xea\xdc\xeb\xb5\xcdVL\xea\x8f\x08\x00' |
|
73 |
+ ), |
|
74 |
+ |
|
75 |
+ }, |
|
76 |
+ 'rsa': { |
|
77 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
78 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn |
|
79 |
+NhAAAAAwEAAQAAAYEAsaHu6Xs4cVsuDSNJlMCqoPVgmDgEviI8TfXmHKqX3JkIqI3LsvV7 |
|
80 |
+Ijf8WCdTveEq7CkuZhImtsR52AOEVAoU8mDXDNr+nJ5wUPzf1UIaRjDe0lcXW4SlF01hQs |
|
81 |
+G4wYDuqxshwelraB/L3e0zhD7fjYHF8IbFsqGlFHWEwOtlfhhfbxJsTGguLm4A8/gdEJD5 |
|
82 |
+2rkqDcZpIXCHtJbCzW9aQpWcs/PDw5ylwl/3dB7jfxyfrGz4O3QrzsqhWEsip97mOmwl6q |
|
83 |
+CHbq8V8x9zu89D/H+bG5ijqxhijbjcVUW3lZfw/97gy9J6rG31HNar5H8GycLTFwuCFepD |
|
84 |
+mTEpNgQLKoe8ePIEPq4WHhFUovBdwlrOByUKKqxreyvWt5gkpTARz+9Lt8OjBO3rpqK8sZ |
|
85 |
+VKH3sE3de2RJM3V9PJdmZSs2b8EFK3PsUGdlMPM9pn1uk4uIItKWBmooOynuD8Ll6aPwuW |
|
86 |
+AFn3l8nLLyWdrmmEYzHWXiRjQJxy1Bi5AbHMOWiPAAAFkDPkuBkz5LgZAAAAB3NzaC1yc2 |
|
87 |
+EAAAGBALGh7ul7OHFbLg0jSZTAqqD1YJg4BL4iPE315hyql9yZCKiNy7L1eyI3/FgnU73h |
|
88 |
+KuwpLmYSJrbEedgDhFQKFPJg1wza/pyecFD839VCGkYw3tJXF1uEpRdNYULBuMGA7qsbIc |
|
89 |
+Hpa2gfy93tM4Q+342BxfCGxbKhpRR1hMDrZX4YX28SbExoLi5uAPP4HRCQ+dq5Kg3GaSFw |
|
90 |
+h7SWws1vWkKVnLPzw8OcpcJf93Qe438cn6xs+Dt0K87KoVhLIqfe5jpsJeqgh26vFfMfc7 |
|
91 |
+vPQ/x/mxuYo6sYYo243FVFt5WX8P/e4MvSeqxt9RzWq+R/BsnC0xcLghXqQ5kxKTYECyqH |
|
92 |
+vHjyBD6uFh4RVKLwXcJazgclCiqsa3sr1reYJKUwEc/vS7fDowTt66aivLGVSh97BN3Xtk |
|
93 |
+STN1fTyXZmUrNm/BBStz7FBnZTDzPaZ9bpOLiCLSlgZqKDsp7g/C5emj8LlgBZ95fJyy8l |
|
94 |
+na5phGMx1l4kY0CcctQYuQGxzDlojwAAAAMBAAEAAAF/cNVYT+Om4x9+SItcz5bOByGIOj |
|
95 |
+yWUH8f9rRjnr5ILuwabIDgvFaVG+xM1O1hWADqzMnSEcknHRkTYEsqYPykAtxFvjOFEh70 |
|
96 |
+6qRUJ+fVZkqRGEaI3oWyWKTOhcCIYImtONvb0LOv/HQ2H2AXCoeqjST1qr/xSuljBtcB8u |
|
97 |
+wxs3EqaO1yU7QoZpDcMX9plH7Rmc9nNfZcgrnktPk2deX2+Y/A5tzdVgG1IeqYp6CBMLNM |
|
98 |
+uhL0OPdDehgBoDujx+rhkZ1gpo1wcULIM94NL7VSHBPX0Lgh9T+3j1HVP+YnMAvhfOvfct |
|
99 |
+LlbJ06+TYGRAMuF2LPCAZM/m0FEyAurRgWxAjLXm+4kp2GAJXlw82deDkQ+P8cHNT6s9ZH |
|
100 |
+R5YSy3lpZ35594ZMOLR8KqVvhgJGF6i9019BiF91SDxjE+sp6dNGfN8W+64tHdDv2a0Mso |
|
101 |
++8Qjyx7sTpi++EjLU8Iy73/e4B8qbXMyheyA/UUfgMtNKShh6sLlrD9h2Sm9RFTuEAAADA |
|
102 |
+Jh3u7WfnjhhKZYbAW4TsPNXDMrB0/t7xyAQgFmko7JfESyrJSLg1cO+QMOiDgD7zuQ9RSp |
|
103 |
+NIKdPsnIna5peh979mVjb2HgnikjyJECmBpLdwZKhX7MnIvgKw5lnQXHboEtWCa1N58l7f |
|
104 |
+srzwbi9pFUuUp9dShXNffmlUCjDRsVLbK5C6+iaIQyCWFYK8mc6dpNkIoPKf+Xg+EJCIFQ |
|
105 |
+oITqeu30Gc1+M+fdZc2ghq0b6XLthh/uHEry8b68M5KglMAAAAwQDw1i+IdcvPV/3u/q9O |
|
106 |
+/kzLpKO3tbT89sc1zhjZsDNjDAGluNr6n38iq/XYRZu7UTL9BG+EgFVfIUV7XsYT5e+BPf |
|
107 |
+13VS94rzZ7maCsOlULX+VdMO2zBucHIoec9RUlRZrfB21B2W7YGMhbpoa5lN3lKJQ7afHo |
|
108 |
+dXZUMp0cTFbOmbzJgSzO2/NE7BhVwmvcUzTDJGMMKuxBO6w99YKDKRKm0PNLFDz26rWm9L |
|
109 |
+dNS2MVfVuPMTpzT26HQG4pFageq9cAAADBALzRBXdZF8kbSBa5MTUBVTTzgKQm1C772gJ8 |
|
110 |
+T01DJEXZsVtOv7mUC1/m/by6Hk4tPyvDBuGj9hHq4N7dPqGutHb1q5n0ADuoQjRW7BXw5Q |
|
111 |
+vC2EAD91xexdorIA5BgXU+qltBqzzBVzVtF7+jOZOjfzOlaTX9I5I5veyeTaTxZj1XXUzi |
|
112 |
+btBNdMEJJp7ifucYmoYAAwE7K+VlWagDEK2y8Mte9y9E+N0uO2j+h85sQt/UIb2iE/vhcg |
|
113 |
+Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB |
|
114 |
+-----END OPENSSH PRIVATE KEY----- |
|
115 |
+''', |
|
116 |
+ '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 |
|
117 |
+''', |
|
118 |
+ 'public_key_data': bytes.fromhex(''' |
|
119 |
+ 00 00 00 07 73 73 68 2d 72 73 61 |
|
120 |
+ 00 00 00 03 01 00 01 |
|
121 |
+ 00 00 01 81 00 b1 a1 ee e9 7b 38 71 5b 2e 0d 23 49 94 c0 aa |
|
122 |
+ a0 f5 60 98 38 04 be 22 3c 4d f5 e6 1c aa 97 dc 99 08 a8 8d |
|
123 |
+ cb b2 f5 7b 22 37 fc 58 27 53 bd e1 2a ec 29 2e 66 12 26 b6 |
|
124 |
+ c4 79 d8 03 84 54 0a 14 f2 60 d7 0c da fe 9c 9e 70 50 fc df |
|
125 |
+ d5 42 1a 46 30 de d2 57 17 5b 84 a5 17 4d 61 42 c1 b8 c1 80 |
|
126 |
+ ee ab 1b 21 c1 e9 6b 68 1f cb dd ed 33 84 3e df 8d 81 c5 f0 |
|
127 |
+ 86 c5 b2 a1 a5 14 75 84 c0 eb 65 7e 18 5f 6f 12 6c 4c 68 2e |
|
128 |
+ 2e 6e 00 f3 f8 1d 10 90 f9 da b9 2a 0d c6 69 21 70 87 b4 96 |
|
129 |
+ c2 cd 6f 5a 42 95 9c b3 f3 c3 c3 9c a5 c2 5f f7 74 1e e3 7f |
|
130 |
+ 1c 9f ac 6c f8 3b 74 2b ce ca a1 58 4b 22 a7 de e6 3a 6c 25 |
|
131 |
+ ea a0 87 6e af 15 f3 1f 73 bb cf 43 fc 7f 9b 1b 98 a3 ab 18 |
|
132 |
+ 62 8d b8 dc 55 45 b7 95 97 f0 ff de e0 cb d2 7a ac 6d f5 1c |
|
133 |
+ d6 ab e4 7f 06 c9 c2 d3 17 0b 82 15 ea 43 99 31 29 36 04 0b |
|
134 |
+ 2a 87 bc 78 f2 04 3e ae 16 1e 11 54 a2 f0 5d c2 5a ce 07 25 |
|
135 |
+ 0a 2a ac 6b 7b 2b d6 b7 98 24 a5 30 11 cf ef 4b b7 c3 a3 04 |
|
136 |
+ ed eb a6 a2 bc b1 95 4a 1f 7b 04 dd d7 b6 44 93 37 57 d3 c9 |
|
137 |
+ 76 66 52 b3 66 fc 10 52 b7 3e c5 06 76 53 0f 33 da 67 d6 e9 |
|
138 |
+ 38 b8 82 2d 29 60 66 a2 83 b2 9e e0 fc 2e 5e 9a 3f 0b 96 00 |
|
139 |
+ 59 f7 97 c9 cb 2f 25 9d ae 69 84 63 31 d6 5e 24 63 40 9c 72 |
|
140 |
+ d4 18 b9 01 b1 cc 39 68 8f |
|
141 |
+'''), |
|
142 |
+ 'expected_signature': bytes.fromhex(''' |
|
143 |
+ 00 00 00 07 73 73 68 2d 72 73 61 |
|
144 |
+ 00 00 01 80 a2 10 7c 2e f6 bb 53 a8 74 2a a1 19 99 ad 81 be |
|
145 |
+ 79 9c ed d6 9d 09 4e 6e c5 18 48 33 90 77 99 68 f7 9e 03 5a |
|
146 |
+ cd 4e 18 eb 89 7d 85 a2 ee ae 4a 92 f6 6f ce b9 fe 86 7f 2a |
|
147 |
+ 6b 31 da 6e 1a fe a2 a5 88 b8 44 7f a1 76 73 b3 ec 75 b5 d0 |
|
148 |
+ a6 b9 15 97 65 09 13 7d 94 21 d1 fb 5d 0f 8b 23 04 77 c2 c3 |
|
149 |
+ 55 22 b1 a0 09 8a f5 38 2a d6 7f 1b 87 29 a0 25 d3 25 6f cb |
|
150 |
+ 64 61 07 98 dc 14 c5 84 f8 92 24 5e 50 11 6b 49 e5 f0 cc 29 |
|
151 |
+ cb 29 a9 19 d8 a7 71 1f 91 0b 05 b1 01 4b c2 5f 00 a5 b6 21 |
|
152 |
+ bf f8 2c 9d 67 9b 47 3b 0a 49 6b 79 2d fc 1d ec 0c b0 e5 27 |
|
153 |
+ 22 d5 a9 f8 d3 c3 f9 df 48 68 e9 fb ef 3c dc 26 bf cf ea 29 |
|
154 |
+ 43 01 a6 e3 c5 51 95 f4 66 6d 8a 55 e2 47 ec e8 30 45 4c ae |
|
155 |
+ 47 e7 c9 a4 21 8b 64 ba b6 88 f6 21 f8 73 b9 cb 11 a1 78 75 |
|
156 |
+ 92 c6 5a e5 64 fe ed 42 d9 95 99 e6 2b 6f 3c 16 3c 28 74 a4 |
|
157 |
+ 72 2f 0d 3f 2c 33 67 aa 35 19 8e e7 b5 11 2f b3 f7 6a c5 02 |
|
158 |
+ e2 6f a3 42 e3 62 19 99 03 ea a5 20 e7 a1 e3 bc c8 06 a3 b5 |
|
159 |
+ 7c d6 76 5d df 6f 60 46 83 2a 08 00 d6 d3 d9 a4 c1 41 8c f8 |
|
160 |
+ 60 56 45 81 da 3b a2 16 1f 9e 4e 75 83 17 da c3 53 c3 3e 19 |
|
161 |
+ a4 1b bc d2 29 b8 78 61 2b 78 e6 b1 52 b0 d5 ec de 69 2c 48 |
|
162 |
+ 62 d9 fd d1 9b 6b b0 49 db d3 ff 38 e7 10 d9 2d ce 9f 0d 5e |
|
163 |
+ 09 7b 37 d2 7b c3 bf ce |
|
164 |
+'''), |
|
165 |
+ }, |
|
166 |
+} |
|
167 |
+ |
|
168 |
+UNSUITABLE = { |
|
169 |
+ 'dsa1024': { |
|
170 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
171 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH |
|
172 |
+NzAAAAgQC7KAZXqBGNVLBQPrcMYAoNW54BhD8aIhe7BDWYzJcsaMt72VKSkguZ8+XR7nRa |
|
173 |
+0C/ZsBi+uJp0dpxy9ZMTOWX4u5YPMeQcXEdGExZIfimGqSOAsy6fCld2IfJZJZExcCmhe9 |
|
174 |
+Ssjsd3YSAPJRluOXFQc95MZoR5hMwlIDD8QzrE7QAAABUA99nOZOgd7aHMVGoXpUEBcn7H |
|
175 |
+ossAAACALr2Ag3hxM3rKdxzVUw8fX0VVPXO+3+Kr8hGe0Kc/7NwVaBVL1GQ8fenBuWynpA |
|
176 |
+UbH0wo3h1wkB/8hX6p+S8cnu5rIBlUuVNwLw/bIYohK98LfqTYK/V+g6KD+8m34wvEiXZm |
|
177 |
+qywY54n2bksch1Nqvj/tNpLzExSx/XS0kSM1aigAAACAbQNRPcVEuGDrEcf+xg5tgAejPX |
|
178 |
+BPXr/Jss+Chk64km3mirMYjAWyWYtVcgT+7hOYxtYRin8LyMLqKRmqa0Q5UrvDfChgLhvs |
|
179 |
+G9YSb/Mpw5qm8PiHSafwhkaz/te3+8hKogqoe7sd+tCF06IpJr5k70ACiNtRGqssNF8Elr |
|
180 |
+l1efYAAAH4swlfVrMJX1YAAAAHc3NoLWRzcwAAAIEAuygGV6gRjVSwUD63DGAKDVueAYQ/ |
|
181 |
+GiIXuwQ1mMyXLGjLe9lSkpILmfPl0e50WtAv2bAYvriadHaccvWTEzll+LuWDzHkHFxHRh |
|
182 |
+MWSH4phqkjgLMunwpXdiHyWSWRMXApoXvUrI7Hd2EgDyUZbjlxUHPeTGaEeYTMJSAw/EM6 |
|
183 |
+xO0AAAAVAPfZzmToHe2hzFRqF6VBAXJ+x6LLAAAAgC69gIN4cTN6yncc1VMPH19FVT1zvt |
|
184 |
+/iq/IRntCnP+zcFWgVS9RkPH3pwblsp6QFGx9MKN4dcJAf/IV+qfkvHJ7uayAZVLlTcC8P |
|
185 |
+2yGKISvfC36k2Cv1foOig/vJt+MLxIl2ZqssGOeJ9m5LHIdTar4/7TaS8xMUsf10tJEjNW |
|
186 |
+ooAAAAgG0DUT3FRLhg6xHH/sYObYAHoz1wT16/ybLPgoZOuJJt5oqzGIwFslmLVXIE/u4T |
|
187 |
+mMbWEYp/C8jC6ikZqmtEOVK7w3woYC4b7BvWEm/zKcOapvD4h0mn8IZGs/7Xt/vISqIKqH |
|
188 |
+u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW |
|
189 |
+0gAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UBAgMEBQYH |
|
190 |
+-----END OPENSSH PRIVATE KEY----- |
|
191 |
+''', |
|
192 |
+ 'public_key': rb'''ssh-dss AAAAB3NzaC1kc3MAAACBALsoBleoEY1UsFA+twxgCg1bngGEPxoiF7sENZjMlyxoy3vZUpKSC5nz5dHudFrQL9mwGL64mnR2nHL1kxM5Zfi7lg8x5BxcR0YTFkh+KYapI4CzLp8KV3Yh8lklkTFwKaF71KyOx3dhIA8lGW45cVBz3kxmhHmEzCUgMPxDOsTtAAAAFQD32c5k6B3tocxUahelQQFyfseiywAAAIAuvYCDeHEzesp3HNVTDx9fRVU9c77f4qvyEZ7Qpz/s3BVoFUvUZDx96cG5bKekBRsfTCjeHXCQH/yFfqn5Lxye7msgGVS5U3AvD9shiiEr3wt+pNgr9X6DooP7ybfjC8SJdmarLBjnifZuSxyHU2q+P+02kvMTFLH9dLSRIzVqKAAAAIBtA1E9xUS4YOsRx/7GDm2AB6M9cE9ev8myz4KGTriSbeaKsxiMBbJZi1VyBP7uE5jG1hGKfwvIwuopGaprRDlSu8N8KGAuG+wb1hJv8ynDmqbw+IdJp/CGRrP+17f7yEqiCqh7ux360IXToikmvmTvQAKI21Eaqyw0XwSWuXV59g== test key without passphrase |
|
193 |
+''', |
|
194 |
+ 'public_key_data': bytes.fromhex(''' |
|
195 |
+ 00 00 00 07 73 73 68 2d 64 73 73 |
|
196 |
+ 00 00 00 81 00 |
|
197 |
+ bb 28 06 57 a8 11 8d 54 b0 50 3e b7 0c 60 0a 0d |
|
198 |
+ 5b 9e 01 84 3f 1a 22 17 bb 04 35 98 cc 97 2c 68 |
|
199 |
+ cb 7b d9 52 92 92 0b 99 f3 e5 d1 ee 74 5a d0 2f |
|
200 |
+ d9 b0 18 be b8 9a 74 76 9c 72 f5 93 13 39 65 f8 |
|
201 |
+ bb 96 0f 31 e4 1c 5c 47 46 13 16 48 7e 29 86 a9 |
|
202 |
+ 23 80 b3 2e 9f 0a 57 76 21 f2 59 25 91 31 70 29 |
|
203 |
+ a1 7b d4 ac 8e c7 77 61 20 0f 25 19 6e 39 71 50 |
|
204 |
+ 73 de 4c 66 84 79 84 cc 25 20 30 fc 43 3a c4 ed |
|
205 |
+ 00 00 00 15 00 f7 d9 ce 64 e8 1d ed a1 cc 54 6a |
|
206 |
+ 17 a5 41 01 72 7e c7 a2 cb |
|
207 |
+ 00 00 00 80 |
|
208 |
+ 2e bd 80 83 78 71 33 7a ca 77 1c d5 53 0f 1f 5f |
|
209 |
+ 45 55 3d 73 be df e2 ab f2 11 9e d0 a7 3f ec dc |
|
210 |
+ 15 68 15 4b d4 64 3c 7d e9 c1 b9 6c a7 a4 05 1b |
|
211 |
+ 1f 4c 28 de 1d 70 90 1f fc 85 7e a9 f9 2f 1c 9e |
|
212 |
+ ee 6b 20 19 54 b9 53 70 2f 0f db 21 8a 21 2b df |
|
213 |
+ 0b 7e a4 d8 2b f5 7e 83 a2 83 fb c9 b7 e3 0b c4 |
|
214 |
+ 89 76 66 ab 2c 18 e7 89 f6 6e 4b 1c 87 53 6a be |
|
215 |
+ 3f ed 36 92 f3 13 14 b1 fd 74 b4 91 23 35 6a 28 |
|
216 |
+ 00 00 00 80 |
|
217 |
+ 6d 03 51 3d c5 44 b8 60 eb 11 c7 fe c6 0e 6d 80 |
|
218 |
+ 07 a3 3d 70 4f 5e bf c9 b2 cf 82 86 4e b8 92 6d |
|
219 |
+ e6 8a b3 18 8c 05 b2 59 8b 55 72 04 fe ee 13 98 |
|
220 |
+ c6 d6 11 8a 7f 0b c8 c2 ea 29 19 aa 6b 44 39 52 |
|
221 |
+ bb c3 7c 28 60 2e 1b ec 1b d6 12 6f f3 29 c3 9a |
|
222 |
+ a6 f0 f8 87 49 a7 f0 86 46 b3 fe d7 b7 fb c8 4a |
|
223 |
+ a2 0a a8 7b bb 1d fa d0 85 d3 a2 29 26 be 64 ef |
|
224 |
+ 40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6 |
|
225 |
+'''), |
|
226 |
+ 'expected_signature': None, |
|
227 |
+ }, |
|
228 |
+ 'ecdsa256': { |
|
229 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
230 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS |
|
231 |
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTLbU0zDwsk2Dvp+VYIrsNVf5gWwz2S |
|
232 |
+3SZ8TbxiQRkpnGSVqyIoHJOJc+NQItAa7xlJ/8Z6gfz57Z3apUkaMJm6AAAAuKeY+YinmP |
|
233 |
+mIAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5 |
|
234 |
+Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmb |
|
235 |
+oAAAAhAKIl/3n0pKVIxpZkXTGtii782Qr4yIcvHdpxjO/QsIqKAAAAG3Rlc3Qga2V5IHdp |
|
236 |
+dGhvdXQgcGFzc3BocmFzZQECAwQ= |
|
237 |
+-----END OPENSSH PRIVATE KEY----- |
|
238 |
+''', |
|
239 |
+ 'public_key': rb'''ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMttTTMPCyTYO+n5Vgiuw1V/mBbDPZLdJnxNvGJBGSmcZJWrIigck4lz41Ai0BrvGUn/xnqB/PntndqlSRowmbo= test key without passphrase |
|
240 |
+''', |
|
241 |
+ 'public_key_data': bytes.fromhex(''' |
|
242 |
+ 00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e |
|
243 |
+ 69 73 74 70 32 35 36 |
|
244 |
+ 00 00 00 08 6e 69 73 74 70 32 35 36 |
|
245 |
+ 00 00 00 41 04 |
|
246 |
+ cb 6d 4d 33 0f 0b 24 d8 3b e9 f9 56 08 ae c3 55 |
|
247 |
+ 7f 98 16 c3 3d 92 dd 26 7c 4d bc 62 41 19 29 9c |
|
248 |
+ 64 95 ab 22 28 1c 93 89 73 e3 50 22 d0 1a ef 19 |
|
249 |
+ 49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba |
|
250 |
+'''), |
|
251 |
+ 'expected_signature': None, |
|
252 |
+ }, |
|
253 |
+ 'ecdsa384': { |
|
254 |
+ 'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY----- |
|
255 |
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS |
|
256 |
+1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSgkOjkAvq7v5vHuj3KBL4/EAWcn5hZ |
|
257 |
+DyKcbyV0eBMGFq7hKXQlZqIahLVqeMR0QqmkxNJ2rly2VHcXneq3vZ+9fIsWCOdYk5WP3N |
|
258 |
+ZPzv911Xn7wbEkC7QndD5zKlm4pBUAAADomhj+IZoY/iEAAAATZWNkc2Etc2hhMi1uaXN0 |
|
259 |
+cDM4NAAAAAhuaXN0cDM4NAAAAGEEoJDo5AL6u7+bx7o9ygS+PxAFnJ+YWQ8inG8ldHgTBh |
|
260 |
+au4Sl0JWaiGoS1anjEdEKppMTSdq5ctlR3F53qt72fvXyLFgjnWJOVj9zWT87/ddV5+8Gx |
|
261 |
+JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B |
|
262 |
+2OdqVOVRkwZWRROz0AAAAbdGVzdCBrZXkgd2l0aG91dCBwYXNzcGhyYXNlAQIDBA== |
|
263 |
+-----END OPENSSH PRIVATE KEY----- |
|
264 |
+''', |
|
265 |
+ 'public_key': rb'''ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKCQ6OQC+ru/m8e6PcoEvj8QBZyfmFkPIpxvJXR4EwYWruEpdCVmohqEtWp4xHRCqaTE0nauXLZUdxed6re9n718ixYI51iTlY/c1k/O/3XVefvBsSQLtCd0PnMqWbikFQ== test key without passphrase |
|
266 |
+''', |
|
267 |
+ 'public_key_data': bytes.fromhex(''' |
|
268 |
+ 00 00 00 13 |
|
269 |
+ 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 |
|
270 |
+ 33 38 34 |
|
271 |
+ 00 00 00 08 6e 69 73 74 70 33 38 34 |
|
272 |
+ 00 00 00 61 04 |
|
273 |
+ a0 90 e8 e4 02 fa bb bf 9b c7 ba 3d ca 04 be 3f |
|
274 |
+ 10 05 9c 9f 98 59 0f 22 9c 6f 25 74 78 13 06 16 |
|
275 |
+ ae e1 29 74 25 66 a2 1a 84 b5 6a 78 c4 74 42 a9 |
|
276 |
+ a4 c4 d2 76 ae 5c b6 54 77 17 9d ea b7 bd 9f bd |
|
277 |
+ 7c 8b 16 08 e7 58 93 95 8f dc d6 4f ce ff 75 d5 |
|
278 |
+ 79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15 |
|
279 |
+'''), |
|
280 |
+ 'expected_signature': None, |
|
281 |
+ }, |
|
282 |
+} |
|
283 |
+ |
|
284 |
+def test_client_uint32(): |
|
285 |
+ uint32 = ssh_agent_client.SSHAgentClient.uint32 |
|
286 |
+ assert uint32(16777216) == b'\x01\x00\x00\x00' |
|
287 |
+ |
|
288 |
+@pytest.mark.parametrize(['value', 'exc_type', 'exc_pattern'], [ |
|
289 |
+ (10000000000000000, OverflowError, 'int too big to convert'), |
|
290 |
+ (-1, OverflowError, "can't convert negative int to unsigned"), |
|
291 |
+]) |
|
292 |
+def test_client_uint32_exceptions(value, exc_type, exc_pattern): |
|
293 |
+ uint32 = ssh_agent_client.SSHAgentClient.uint32 |
|
294 |
+ with pytest.raises(exc_type, match=exc_pattern): |
|
295 |
+ uint32(value) |
|
296 |
+ |
|
297 |
+@pytest.mark.parametrize(['input', 'expected'], [ |
|
298 |
+ (b'ssh-rsa', b'\x00\x00\x00\x07ssh-rsa'), |
|
299 |
+ (b'ssh-ed25519', b'\x00\x00\x00\x0bssh-ed25519'), |
|
300 |
+ ( |
|
301 |
+ ssh_agent_client.SSHAgentClient.string(b'ssh-ed25519'), |
|
302 |
+ b'\x00\x00\x00\x0f\x00\x00\x00\x0bssh-ed25519', |
|
303 |
+ ), |
|
304 |
+]) |
|
305 |
+def test_client_string(input, expected): |
|
306 |
+ string = ssh_agent_client.SSHAgentClient.string |
|
307 |
+ assert bytes(string(input)) == expected |
|
308 |
+ |
|
309 |
+@pytest.mark.parametrize(['input', 'expected'], [ |
|
310 |
+ (b'\x00\x00\x00\x07ssh-rsa', b'ssh-rsa'), |
|
311 |
+ ( |
|
312 |
+ ssh_agent_client.SSHAgentClient.string(b'ssh-ed25519'), |
|
313 |
+ b'ssh-ed25519', |
|
314 |
+ ), |
|
315 |
+]) |
|
316 |
+def test_client_unstring(input, expected): |
|
317 |
+ unstring = ssh_agent_client.SSHAgentClient.unstring |
|
318 |
+ assert bytes(unstring(input)) == expected |
|
319 |
+ |
|
320 |
+@pytest.mark.parametrize(['input', 'exc_type', 'exc_pattern'], [ |
|
321 |
+ (b'ssh', ValueError, 'malformed SSH byte string'), |
|
322 |
+ (b'\x00\x00\x00\x08ssh-rsa', ValueError, 'malformed SSH byte string'), |
|
323 |
+ ( |
|
324 |
+ b'\x00\x00\x00\x04XXX trailing text', |
|
325 |
+ ValueError, 'malformed SSH byte string', |
|
326 |
+ ), |
|
327 |
+]) |
|
328 |
+def test_client_unstring_exceptions(input, exc_type, exc_pattern): |
|
329 |
+ unstring = ssh_agent_client.SSHAgentClient.unstring |
|
330 |
+ with pytest.raises(exc_type, match=exc_pattern): |
|
331 |
+ unstring(input) |
|
332 |
+ |
|
333 |
+def test_key_decoding(): |
|
334 |
+ public_key = SUPPORTED['ed25519']['public_key'] |
|
335 |
+ public_key_data = SUPPORTED['ed25519']['public_key_data'] |
|
336 |
+ keydata = base64.b64decode(public_key.split(None, 2)[1]) |
|
337 |
+ assert ( |
|
338 |
+ keydata == public_key_data |
|
339 |
+ ), "recorded public key data doesn't match" |
|
340 |
+ |
|
341 |
+@pytest.mark.parametrize(['keytype', 'data_dict'], list(SUPPORTED.items())) |
|
342 |
+def test_sign_data_via_agent(keytype, data_dict): |
|
343 |
+ private_key = data_dict['private_key'] |
|
344 |
+ try: |
|
345 |
+ result = subprocess.run(['ssh-add', '-t', '30', '-q', '-'], |
|
346 |
+ input=private_key, check=True, |
|
347 |
+ capture_output=True) |
|
348 |
+ except subprocess.CalledProcessError as e: |
|
349 |
+ pytest.skip( |
|
350 |
+ f"uploading test key: {e!r}, stdout={e.stdout!r}, " |
|
351 |
+ f"stderr={e.stderr!r}" |
|
352 |
+ ) |
|
353 |
+ else: |
|
354 |
+ try: |
|
355 |
+ client = ssh_agent_client.SSHAgentClient() |
|
356 |
+ except OSError: |
|
357 |
+ pytest.skip('communication error with the SSH agent') |
|
358 |
+ with client: |
|
359 |
+ key_comment_pairs = {bytes(k): bytes(c) |
|
360 |
+ for k, c in client.list_keys()} |
|
361 |
+ public_key_data = data_dict['public_key_data'] |
|
362 |
+ expected_signature = data_dict['expected_signature'] |
|
363 |
+ if public_key_data not in key_comment_pairs: |
|
364 |
+ pytest.skip('prerequisite SSH key not loaded') |
|
365 |
+ signature = bytes(client.sign( |
|
366 |
+ payload=derivepassphrase.Vault._UUID, key=public_key_data)) |
|
367 |
+ assert signature == expected_signature, 'SSH signature mismatch' |
|
368 |
+ signature2 = bytes(client.sign( |
|
369 |
+ payload=derivepassphrase.Vault._UUID, key=public_key_data)) |
|
370 |
+ assert signature2 == expected_signature, 'SSH signature mismatch' |
|
371 |
+ assert ( |
|
372 |
+ derivepassphrase.Vault.phrase_from_signature(public_key_data) == |
|
373 |
+ expected_signature |
|
374 |
+ ), 'SSH signature mismatch' |
|
375 |
+ |
|
376 |
+@pytest.mark.parametrize(['keytype', 'data_dict'], list(UNSUITABLE.items())) |
|
377 |
+def test_sign_data_via_agent_unsupported(keytype, data_dict): |
|
378 |
+ private_key = data_dict['private_key'] |
|
379 |
+ try: |
|
380 |
+ result = subprocess.run(['ssh-add', '-t', '30', '-q', '-'], |
|
381 |
+ input=private_key, check=True, |
|
382 |
+ capture_output=True) |
|
383 |
+ except subprocess.CalledProcessError as e: |
|
384 |
+ pytest.xfail( |
|
385 |
+ f"uploading test key: {e!r}, stdout={e.stdout!r}, " |
|
386 |
+ f"stderr={e.stderr!r}" |
|
387 |
+ ) |
|
388 |
+ else: |
|
389 |
+ try: |
|
390 |
+ client = ssh_agent_client.SSHAgentClient() |
|
391 |
+ except OSError: |
|
392 |
+ pytest.skip('communication error with the SSH agent') |
|
393 |
+ with client: |
|
394 |
+ key_comment_pairs = {bytes(k): bytes(c) |
|
395 |
+ for k, c in client.list_keys()} |
|
396 |
+ public_key_data = data_dict['public_key_data'] |
|
397 |
+ expected_signature = data_dict['expected_signature'] |
|
398 |
+ if public_key_data not in key_comment_pairs: |
|
399 |
+ pytest.skip('prerequisite SSH key not loaded') |
|
400 |
+ signature = bytes(client.sign( |
|
401 |
+ payload=derivepassphrase.Vault._UUID, key=public_key_data)) |
|
402 |
+ signature2 = bytes(client.sign( |
|
403 |
+ payload=derivepassphrase.Vault._UUID, key=public_key_data)) |
|
404 |
+ assert signature != signature2, 'SSH signature repeatable?!' |
|
405 |
+ with pytest.raises(ValueError, match='unsuitable SSH key'): |
|
406 |
+ derivepassphrase.Vault.phrase_from_signature(public_key_data) |
... | ... |
@@ -0,0 +1,90 @@ |
1 |
+# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: MIT |
|
4 |
+ |
|
5 |
+"""Test passphrase generation via derivepassphrase.Vault.""" |
|
6 |
+ |
|
7 |
+import pytest |
|
8 |
+ |
|
9 |
+import derivepassphrase |
|
10 |
+import sequin |
|
11 |
+ |
|
12 |
+Vault = derivepassphrase.Vault |
|
13 |
+phrase = b'She cells C shells bye the sea shoars' |
|
14 |
+ |
|
15 |
+@pytest.mark.parametrize('service,expected', [ |
|
16 |
+ (b'google', rb': 4TVH#5:aZl8LueOT\{'), |
|
17 |
+ ('twitter', rb"[ (HN_N:lI&<ro=)3'g9"), |
|
18 |
+]) |
|
19 |
+def test_200_basic_configuration(service, expected): |
|
20 |
+ assert Vault(phrase=phrase).generate(service) == expected |
|
21 |
+ |
|
22 |
+def test_201_phrase_dependence(): |
|
23 |
+ assert ( |
|
24 |
+ Vault(phrase=(phrase + b'X')).generate('google') == |
|
25 |
+ b'n+oIz6sL>K*lTEWYRO%7' |
|
26 |
+ ) |
|
27 |
+ |
|
28 |
+def test_202_reproducibility_and_bytes_service_name(): |
|
29 |
+ assert ( |
|
30 |
+ Vault(phrase=phrase).generate(b'google') == |
|
31 |
+ Vault(phrase=phrase).generate('google') |
|
32 |
+ ) |
|
33 |
+ |
|
34 |
+def test_210_nonstandard_length(): |
|
35 |
+ assert Vault(phrase=phrase, length=4).generate('google') == b'xDFu' |
|
36 |
+ |
|
37 |
+def test_211_repetition_limit(): |
|
38 |
+ assert ( |
|
39 |
+ Vault(phrase=b'', length=24, symbol=0, number=0, |
|
40 |
+ repeat=1).generate('asd') == |
|
41 |
+ b'IVTDzACftqopUXqDHPkuCIhV' |
|
42 |
+ ) |
|
43 |
+ |
|
44 |
+def test_212_without_symbols(): |
|
45 |
+ assert ( |
|
46 |
+ Vault(phrase=phrase, symbol=0).generate('google') == |
|
47 |
+ b'XZ4wRe0bZCazbljCaMqR' |
|
48 |
+ ) |
|
49 |
+ |
|
50 |
+def test_213_too_many_symbols(): |
|
51 |
+ with pytest.raises(ValueError, |
|
52 |
+ match='requested passphrase length too short'): |
|
53 |
+ Vault(phrase=phrase, symbol=100) |
|
54 |
+ |
|
55 |
+def test_214_no_numbers(): |
|
56 |
+ assert ( |
|
57 |
+ Vault(phrase=phrase, number=0).generate('google') == |
|
58 |
+ b'_*$TVH.%^aZl(LUeOT?>' |
|
59 |
+ ) |
|
60 |
+ |
|
61 |
+def test_214_no_lowercase_letters(): |
|
62 |
+ assert ( |
|
63 |
+ Vault(phrase=phrase, lower=0).generate('google') == |
|
64 |
+ b':{?)+7~@OA:L]!0E$)(+' |
|
65 |
+ ) |
|
66 |
+ |
|
67 |
+def test_215_at_least_5_digits(): |
|
68 |
+ assert ( |
|
69 |
+ Vault(phrase=phrase, length=8, number=5).generate('songkick') == |
|
70 |
+ b'i0908.7[' |
|
71 |
+ ) |
|
72 |
+ |
|
73 |
+def test_216_lots_of_spaces(): |
|
74 |
+ assert ( |
|
75 |
+ Vault(phrase=phrase, space=12).generate('songkick') == |
|
76 |
+ b' c 6 Bq % 5fR ' |
|
77 |
+ ) |
|
78 |
+ |
|
79 |
+def test_217_no_viable_characters(): |
|
80 |
+ with pytest.raises(ValueError, |
|
81 |
+ match='no allowed characters left'): |
|
82 |
+ Vault(phrase=phrase, lower=0, upper=0, number=0, |
|
83 |
+ space=0, dash=0, symbol=0) |
|
84 |
+ |
|
85 |
+def test_218_all_character_classes(): |
|
86 |
+ assert ( |
|
87 |
+ Vault(phrase=phrase, lower=2, upper=2, number=1, |
|
88 |
+ space=3, dash=2, symbol=1).generate('google') == |
|
89 |
+ b': : fv_wqt>a-4w1S R' |
|
90 |
+ ) |
... | ... |
@@ -0,0 +1,105 @@ |
1 |
+# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info> |
|
2 |
+# |
|
3 |
+# SPDX-License-Identifier: MIT |
|
4 |
+ |
|
5 |
+"""Test sequin.Sequin.""" |
|
6 |
+ |
|
7 |
+import pytest |
|
8 |
+ |
|
9 |
+import sequin |
|
10 |
+ |
|
11 |
+import collections |
|
12 |
+ |
|
13 |
+benum = sequin.Sequin._big_endian_number |
|
14 |
+ |
|
15 |
+@pytest.mark.parametrize(['sequence', 'base', 'expected'], [ |
|
16 |
+ ([1, 2, 3, 4, 5, 6], 10, 123456), |
|
17 |
+ ([1, 2, 3, 4, 5, 6], 100, 10203040506), |
|
18 |
+ ([0, 0, 1, 4, 9, 7], 10, 1497), |
|
19 |
+ ([1, 0, 0, 1, 0, 0, 0, 0], 2, 144), |
|
20 |
+ ([1, 7, 5, 5], 8, 0o1755), |
|
21 |
+]) |
|
22 |
+def test_big_endian_number(sequence, base, expected): |
|
23 |
+ assert benum(sequence, base=base) == expected |
|
24 |
+ |
|
25 |
+@pytest.mark.parametrize(['exc_type', 'exc_pattern', 'sequence' , 'base'], [ |
|
26 |
+ (ValueError, 'invalid base 3 digit:', [-1], 3), |
|
27 |
+ (ValueError, 'invalid base:', [0], 1), |
|
28 |
+]) |
|
29 |
+def test_big_endian_number_exceptions(exc_type, exc_pattern, sequence, base): |
|
30 |
+ with pytest.raises(exc_type, match=exc_pattern): |
|
31 |
+ benum(sequence, base=base) |
|
32 |
+ |
|
33 |
+@pytest.mark.parametrize(['sequence', 'is_bitstring', 'expected'], [ |
|
34 |
+ ([1, 0, 0, 1, 0, 1], False, [0, 0, 0, 0, 0, 0, 0, 1, |
|
35 |
+ 0, 0, 0, 0, 0, 0, 0, 0, |
|
36 |
+ 0, 0, 0, 0, 0, 0, 0, 0, |
|
37 |
+ 0, 0, 0, 0, 0, 0, 0, 1, |
|
38 |
+ 0, 0, 0, 0, 0, 0, 0, 0, |
|
39 |
+ 0, 0, 0, 0, 0, 0, 0, 1]), |
|
40 |
+ ([1, 0, 0, 1, 0, 1], True, [1, 0, 0, 1, 0, 1]), |
|
41 |
+ (b'OK', False, [0, 1, 0, 0, 1, 1, 1, 1, |
|
42 |
+ 0, 1, 0, 0, 1, 0, 1, 1]), |
|
43 |
+ ('OK', False, [0, 1, 0, 0, 1, 1, 1, 1, |
|
44 |
+ 0, 1, 0, 0, 1, 0, 1, 1]), |
|
45 |
+]) |
|
46 |
+def test_constructor(sequence, is_bitstring, expected): |
|
47 |
+ seq = sequin.Sequin(sequence, is_bitstring=is_bitstring) |
|
48 |
+ assert seq.bases == {2: collections.deque(expected)} |
|
49 |
+ |
|
50 |
+@pytest.mark.parametrize( |
|
51 |
+ ['sequence', 'is_bitstring', 'exc_type', 'exc_pattern'], |
|
52 |
+ [ |
|
53 |
+ ([0, 1, 2, 3, 4, 5, 6, 7], True, |
|
54 |
+ ValueError, 'sequence item out of range'), |
|
55 |
+ (u'こんにちは。', False, |
|
56 |
+ ValueError, 'sequence item out of range'), |
|
57 |
+ ] |
|
58 |
+) |
|
59 |
+def test_constructor_exceptions(sequence, is_bitstring, exc_type, exc_pattern): |
|
60 |
+ with pytest.raises(exc_type, match=exc_pattern): |
|
61 |
+ sequin.Sequin(sequence, is_bitstring=is_bitstring) |
|
62 |
+ |
|
63 |
+def test_shifting(): |
|
64 |
+ seq = sequin.Sequin([1, 0, 1, 0, 0, 1, 0, 0, 0, 1], is_bitstring=True) |
|
65 |
+ assert seq.bases == {2: collections.deque([1, 0, 1, 0, 0, 1, 0, 0, 0, 1])} |
|
66 |
+ # |
|
67 |
+ assert seq._all_or_nothing_shift(3) == (1, 0, 1) |
|
68 |
+ assert seq._all_or_nothing_shift(3) == (0, 0, 1) |
|
69 |
+ assert seq.bases[2] == collections.deque([0, 0, 0, 1]) |
|
70 |
+ # |
|
71 |
+ assert seq._all_or_nothing_shift(5) == () |
|
72 |
+ assert seq.bases[2] == collections.deque([0, 0, 0, 1]) |
|
73 |
+ # |
|
74 |
+ assert seq._all_or_nothing_shift(4), (0, 0, 0, 1) |
|
75 |
+ assert 2 not in seq.bases |
|
76 |
+ |
|
77 |
+def test_generating(): |
|
78 |
+ seq = sequin.Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
79 |
+ is_bitstring=True) |
|
80 |
+ assert seq.generate(1) == 0 |
|
81 |
+ assert seq.generate(5) == 3 |
|
82 |
+ assert seq.generate(5) == 3 |
|
83 |
+ assert seq.generate(5) == 1 |
|
84 |
+ with pytest.raises(sequin.SequinExhaustedException): |
|
85 |
+ seq.generate(5) |
|
86 |
+ with pytest.raises(sequin.SequinExhaustedException): |
|
87 |
+ seq.generate(1) |
|
88 |
+ seq = sequin.Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
89 |
+ is_bitstring=True) |
|
90 |
+ with pytest.raises(ValueError, match='invalid target range'): |
|
91 |
+ seq.generate(0) |
|
92 |
+ |
|
93 |
+def test_internal_generating(): |
|
94 |
+ seq = sequin.Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
95 |
+ is_bitstring=True) |
|
96 |
+ assert seq._generate_inner(5) == 3 |
|
97 |
+ assert seq._generate_inner(5) == 3 |
|
98 |
+ assert seq._generate_inner(5) == 1 |
|
99 |
+ assert seq._generate_inner(5) == 5 |
|
100 |
+ assert seq._generate_inner(1) == 0 |
|
101 |
+ seq = sequin.Sequin([1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], |
|
102 |
+ is_bitstring=True) |
|
103 |
+ assert seq._generate_inner(1) == 0 |
|
104 |
+ with pytest.raises(ValueError, match='invalid target range'): |
|
105 |
+ seq._generate_inner(0) |
|
0 | 106 |