Add more tests of the storeroom format exporter's internals
Marco Ricci

Marco Ricci commited on 2024-08-31 21:24:29
Zeige 2 geänderte Dateien mit 155 Einfügungen und 1 Löschungen.


Further tests are necessary to achieve 100% coverage, but these concern
the detection of format errors, so the test files are rather hard to
generate.
... ...
@@ -440,6 +440,51 @@ VAULT_STOREROOM_CONFIG_DATA = {
440 440
     },
441 441
 }
442 442
 
443
+_VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED_JAVASCRIPT_SOURCE = """
444
+// Executed in the top-level directory of the vault project code, in Node.js.
445
+const storeroom = require('storeroom')
446
+const Store = require('./lib/store.js')
447
+let store = new Store(storeroom.createFileAdapter('./broken-dir', 'vault key'))
448
+await store._storeroom.put('/services/array/', ['entry1','entry2'])
449
+// The resulting "broken-dir" was then zipped manually.
450
+"""
451
+VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED = b"""
452
+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
486
+"""
487
+
443 488
 CANNOT_LOAD_CRYPTOGRAPHY = (
444 489
     b'Cannot load the required Python module "cryptography".'
445 490
 )
... ...
@@ -11,7 +11,7 @@ import click.testing
11 11
 import pytest
12 12
 
13 13
 import tests
14
-from derivepassphrase.exporter import cli
14
+from derivepassphrase.exporter import cli, storeroom
15 15
 
16 16
 cryptography = pytest.importorskip('cryptography', minversion='38.0')
17 17
 
... ...
@@ -194,3 +194,112 @@ class TestCLI:
194 194
         assert result.stderr_bytes
195 195
         assert b'Invalid vault config: ' in result.stderr_bytes
196 196
         assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr_bytes
197
+
198
+
199
+class TestStoreroom:
200
+    @pytest.mark.parametrize(
201
+        ['path', 'key'],
202
+        [
203
+            ('.vault', tests.VAULT_MASTER_KEY),
204
+            ('.vault', None),
205
+            (None, tests.VAULT_MASTER_KEY),
206
+            (None, None),
207
+        ],
208
+    )
209
+    def test_200_export_data_path_and_keys_type(
210
+        self,
211
+        monkeypatch: pytest.MonkeyPatch,
212
+        path: str | None,
213
+        key: str | None,
214
+    ) -> None:
215
+        runner = click.testing.CliRunner(mix_stderr=False)
216
+        with tests.isolated_vault_exporter_config(
217
+            monkeypatch=monkeypatch,
218
+            runner=runner,
219
+            vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED,
220
+            vault_key=tests.VAULT_MASTER_KEY,
221
+        ):
222
+            assert (
223
+                storeroom.export_storeroom_data(path, key)
224
+                == tests.VAULT_STOREROOM_CONFIG_DATA
225
+            )
226
+
227
+    def test_400_decrypt_bucket_item_unknown_version(self) -> None:
228
+        bucket_item = (
229
+            b'\xff' + bytes(storeroom.ENCRYPTED_KEYPAIR_SIZE) + bytes(3)
230
+        )
231
+        master_keys: storeroom.MasterKeys = {
232
+            'encryption_key': bytes(storeroom.KEY_SIZE),
233
+            'signing_key': bytes(storeroom.KEY_SIZE),
234
+            'hashing_key': bytes(storeroom.KEY_SIZE),
235
+        }
236
+        with pytest.raises(RuntimeError, match='Cannot handle version 255'):
237
+            storeroom.decrypt_bucket_item(bucket_item, master_keys)
238
+
239
+    @pytest.mark.parametrize('config', ['xxx', 'null', '{"version": 255}'])
240
+    def test_401_decrypt_bucket_file_bad_json_or_version(
241
+        self,
242
+        monkeypatch: pytest.MonkeyPatch,
243
+        config: str,
244
+    ) -> None:
245
+        runner = click.testing.CliRunner(mix_stderr=False)
246
+        master_keys: storeroom.MasterKeys = {
247
+            'encryption_key': bytes(storeroom.KEY_SIZE),
248
+            'signing_key': bytes(storeroom.KEY_SIZE),
249
+            'hashing_key': bytes(storeroom.KEY_SIZE),
250
+        }
251
+        with (
252
+            tests.isolated_vault_exporter_config(
253
+                monkeypatch=monkeypatch,
254
+                runner=runner,
255
+                vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED,
256
+            ),
257
+        ):
258
+            with open('.vault/20', 'w', encoding='UTF-8') as outfile:
259
+                print(config, file=outfile)
260
+            with pytest.raises(RuntimeError, match='Invalid bucket file: '):
261
+                list(storeroom.decrypt_bucket_file('.vault/20', master_keys))
262
+
263
+    @pytest.mark.parametrize(
264
+        ['data', 'err_msg'],
265
+        [
266
+            ('{"version": 255}', 'bad or unsupported keys version header'),
267
+            ('{"version": 1}\nAAAA\nAAAA', 'trailing data; cannot make sense'),
268
+            ('{"version": 1}\nAAAA', 'cannot handle version 0 encrypted keys'),
269
+        ],
270
+    )
271
+    def test_402_export_storeroom_data_bad_master_keys_file(
272
+        self,
273
+        monkeypatch: pytest.MonkeyPatch,
274
+        data: str,
275
+        err_msg: str,
276
+    ) -> None:
277
+        runner = click.testing.CliRunner(mix_stderr=False)
278
+        with (
279
+            tests.isolated_vault_exporter_config(
280
+                monkeypatch=monkeypatch,
281
+                runner=runner,
282
+                vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED,
283
+                vault_key=tests.VAULT_MASTER_KEY,
284
+            ),
285
+        ):
286
+            with open('.vault/.keys', 'w', encoding='UTF-8') as outfile:
287
+                print(data, file=outfile)
288
+            with pytest.raises(RuntimeError, match=err_msg):
289
+                storeroom.export_storeroom_data()
290
+
291
+    def test_403_export_storeroom_data_bad_directory_listing(
292
+        self,
293
+        monkeypatch: pytest.MonkeyPatch,
294
+    ) -> None:
295
+        runner = click.testing.CliRunner(mix_stderr=False)
296
+        with (
297
+            tests.isolated_vault_exporter_config(
298
+                monkeypatch=monkeypatch,
299
+                runner=runner,
300
+                vault_config=tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED,
301
+                vault_key=tests.VAULT_MASTER_KEY,
302
+            ),
303
+            pytest.raises(RuntimeError, match='Object key mismatch'),
304
+        ):
305
+                storeroom.export_storeroom_data()
197 306