Fix passphrase-from-SSH-signature calculation
Marco Ricci

Marco Ricci commited on 2024-06-22 21:19:30
Zeige 2 geänderte Dateien mit 22 Einfügungen und 10 Löschungen.


In vault(1), the passphrase derived from an SSH key signature is *not*
just the (framed, binary) SSH signature of the vault UUID.  Instead, the
payload part of the signature, in binary, is converted to base64,
yielding the passphrase.  Rewrite the `Vault.phrase_from_signature`
method to match vault's behavior, and rename it to `phrase_from_key` to
better match the *actual* interface (which takes an SSH key, not
a signature).

(This design – emitting an SSH signature as payload only, in base64 – is
hard-coded into the SSH agent client library used by vault, and likely
not a conscious design by vault's primary author.)
... ...
@@ -232,7 +232,7 @@ class Vault:
232 232
             target lengths.
233 233
 
234 234
         Examples:
235
-            >>> # See also Vault.phrase_from_signature examples.
235
+            >>> # See also Vault.phrase_from_key examples.
236 236
             >>> phrase = bytes.fromhex('''
237 237
             ... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
238 238
             ... 00 00 00 40
... ...
@@ -345,9 +345,9 @@ class Vault:
345 345
         return any(v(key) for v in deterministic_signature_types.values())
346 346
 
347 347
     @classmethod
348
-    def phrase_from_signature(
348
+    def phrase_from_key(
349 349
         cls, key: bytes | bytearray, /
350
-    ) -> bytes | bytearray:
350
+    ) -> bytes:
351 351
         """Obtain the master passphrase from a configured SSH key.
352 352
 
353 353
         vault allows the usage of certain SSH keys to derive a master
... ...
@@ -358,7 +358,8 @@ class Vault:
358 358
             key: The (public) SSH key to use for signing.
359 359
 
360 360
         Returns:
361
-            The signature of the vault UUID under this key.
361
+            The signature of the vault UUID under this key, unframed but
362
+            encoded in base64.
362 363
 
363 364
         Raises:
364 365
             ValueError:
... ...
@@ -367,14 +368,15 @@ class Vault:
367 368
                 deterministic.
368 369
 
369 370
         Examples:
370
-            >>> # Actual test public key.
371
+            >>> import base64
372
+            >>> # Actual Ed25519 test public key.
371 373
             >>> public_key = bytes.fromhex('''
372 374
             ... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
373 375
             ... 00 00 00 20
374 376
             ... 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1
375 377
             ... 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76
376 378
             ... ''')
377
-            >>> expected_sig = bytes.fromhex('''
379
+            >>> expected_sig_raw = bytes.fromhex('''
378 380
             ... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
379 381
             ... 00 00 00 40
380 382
             ... f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86
... ...
@@ -382,7 +384,10 @@ class Vault:
382 384
             ... 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
383 385
             ... 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
384 386
             ... ''')
385
-            >>> Vault.phrase_from_signature(public_key) == expected_sig  # doctest:+SKIP
387
+            >>> # Raw Ed25519 signatures are 64 bytes long.
388
+            >>> signature_blob = expected_sig_raw[-64:]
389
+            >>> phrase = base64.standard_b64encode(signature_blob)
390
+            >>> Vault.phrase_from_key(phrase) == expected  # doctest:+SKIP
386 391
             True
387 392
 
388 393
         """
... ...
@@ -390,8 +395,10 @@ class Vault:
390 395
             raise ValueError(
391 396
                 'unsuitable SSH key: bad key, or signature not deterministic')
392 397
         with ssh_agent_client.SSHAgentClient() as client:
393
-            ret = client.sign(key, cls._UUID)
394
-        return ret
398
+            raw_sig = client.sign(key, cls._UUID)
399
+        keytype, trailer = client.unstring_prefix(raw_sig)
400
+        signature_blob = client.unstring(trailer)
401
+        return bytes(base64.standard_b64encode(signature_blob))
395 402
 
396 403
     @staticmethod
397 404
     def _subtract(
... ...
@@ -40,6 +40,7 @@ idwcakUGCekJD/vCEml2AAAAG3Rlc3Qga2V5IHdpdGhvdXQgcGFzc3BocmFzZQEC
40 40
             0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
41 41
             1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
42 42
         '''),
43
+        'derived_passphrase': rb'8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==',
43 44
     },
44 45
     # Currently only supported by PuTTY (which is deficient in other
45 46
     # niceties of the SSH agent and the agent's client).
... ...
@@ -77,7 +78,7 @@ dGhvdXQgcGFzc3BocmFzZQECAwQFBgcICQ==
77 78
             db bd 77 7c 80 20 7f 3a 48 61 f6 1f ae a9 5e 53
78 79
             7b e0 9d 93 1e ea dc eb b5 cd 56 4c ea 8f 08 00
79 80
         '''),
80
-
81
+        'derived_passphrase': rb'Bob0ZKSmutnDIsSTSZn8Ed5nlwjy2Lc8LBPnxRwekqYO2C9tgQOCAONy5DJtctJtMoQ/zKkeVywAmrOZ3kXazi7R2+WJ8zW+JFiQxsoE8NuIgNu9d3yAIH86SGH2H66pXlN74J2THurc67XNVkzqjwgA',
81 82
     },
82 83
     'rsa': {
83 84
         'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY-----
... ...
@@ -178,6 +179,7 @@ Bgp6142WnSCQAAABt0ZXN0IGtleSB3aXRob3V0IHBhc3NwaHJhc2UB
178 179
             de 69 2c 48 62 d9 fd d1 9b 6b b0 49 db d3 ff 38
179 180
             e7 10 d9 2d ce 9f 0d 5e 09 7b 37 d2 7b c3 bf ce
180 181
 '''),
182
+        'derived_passphrase': rb'ohB8Lva7U6h0KqEZma2Bvnmc7dadCU5uxRhIM5B3mWj3ngNazU4Y64l9haLurkqS9m/Ouf6GfyprMdpuGv6ipYi4RH+hdnOz7HW10Ka5FZdlCRN9lCHR+10PiyMEd8LDVSKxoAmK9Tgq1n8bhymgJdMlb8tkYQeY3BTFhPiSJF5QEWtJ5fDMKcspqRnYp3EfkQsFsQFLwl8ApbYhv/gsnWebRzsKSWt5Lfwd7Ayw5Sci1an408P530ho6fvvPNwmv8/qKUMBpuPFUZX0Zm2KVeJH7OgwRUyuR+fJpCGLZLq2iPYh+HO5yxGheHWSxlrlZP7tQtmVmeYrbzwWPCh0pHIvDT8sM2eqNRmO57URL7P3asUC4m+jQuNiGZkD6qUg56HjvMgGo7V81nZd329gRoMqCADW09mkwUGM+GBWRYHaO6IWH55OdYMX2sNTwz4ZpBu80im4eGEreOaxUrDV7N5pLEhi2f3Rm2uwSdvT/zjnENktzp8NXgl7N9J7w7/O',
181 183
     },
182 184
 }
183 185
 
... ...
@@ -240,6 +242,7 @@ u7HfrQhdOiKSa+ZO9AAojbURqrLDRfBJa5dXn2AAAAFQDJHfenj4EJ9WkehpdJatPBlqCW
240 242
             40 02 88 db 51 1a ab 2c 34 5f 04 96 b9 75 79 f6
241 243
 '''),
242 244
         'expected_signature': None,
245
+        'derived_passphrase': None,
243 246
     },
244 247
     'ecdsa256': {
245 248
         'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY-----
... ...
@@ -265,6 +268,7 @@ dGhvdXQgcGFzc3BocmFzZQECAwQ=
265 268
             49 ff c6 7a 81 fc f9 ed 9d da a5 49 1a 30 99 ba
266 269
 '''),
267 270
         'expected_signature': None,
271
+        'derived_passphrase': None,
268 272
     },
269 273
     'ecdsa384': {
270 274
         'private_key': rb'''-----BEGIN OPENSSH PRIVATE KEY-----
... ...
@@ -294,6 +298,7 @@ JAu0J3Q+cypZuKQVAAAAMQD5sTy8p+B1cn/DhOmXquui1BcxvASqzzevkBlbQoBa73y04B
294 298
             79 fb c1 b1 24 0b b4 27 74 3e 73 2a 59 b8 a4 15
295 299
 '''),
296 300
         'expected_signature': None,
301
+        'derived_passphrase': None,
297 302
     },
298 303
 }
299 304
 
300 305