acquire 3.17.dev1__tar.gz → 3.17.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {acquire-3.17.dev1/acquire.egg-info → acquire-3.17.dev2}/PKG-INFO +1 -1
  2. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/crypt.py +1 -1
  3. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/outputs/zip.py +1 -1
  4. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/tools/decrypter.py +38 -24
  5. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/version.py +2 -2
  6. {acquire-3.17.dev1 → acquire-3.17.dev2/acquire.egg-info}/PKG-INFO +1 -1
  7. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire.egg-info/SOURCES.txt +2 -0
  8. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/conftest.py +6 -0
  9. acquire-3.17.dev2/tests/data/private_key.pem +28 -0
  10. acquire-3.17.dev2/tests/data/public_key.pem +9 -0
  11. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_outputs_tar.py +18 -0
  12. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_outputs_zip.py +18 -0
  13. {acquire-3.17.dev1 → acquire-3.17.dev2}/COPYRIGHT +0 -0
  14. {acquire-3.17.dev1 → acquire-3.17.dev2}/LICENSE +0 -0
  15. {acquire-3.17.dev1 → acquire-3.17.dev2}/MANIFEST.in +0 -0
  16. {acquire-3.17.dev1 → acquire-3.17.dev2}/README.md +0 -0
  17. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/__init__.py +0 -0
  18. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/acquire.py +0 -0
  19. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/collector.py +0 -0
  20. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/__init__.py +0 -0
  21. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/__init__.py +0 -0
  22. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/collect.py +0 -0
  23. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/exceptions.py +0 -0
  24. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/handles.py +0 -0
  25. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/named_objects.py +0 -0
  26. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/ntdll.py +0 -0
  27. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/dynamic/windows/types.py +0 -0
  28. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/esxi.py +0 -0
  29. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/gui/__init__.py +0 -0
  30. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/gui/base.py +0 -0
  31. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/gui/win32.py +0 -0
  32. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/hashes.py +0 -0
  33. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/log.py +0 -0
  34. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/outputs/__init__.py +0 -0
  35. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/outputs/base.py +0 -0
  36. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/outputs/dir.py +0 -0
  37. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/outputs/tar.py +0 -0
  38. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/tools/__init__.py +0 -0
  39. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/uploaders/__init__.py +0 -0
  40. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/uploaders/minio.py +0 -0
  41. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/uploaders/plugin.py +0 -0
  42. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/uploaders/plugin_registry.py +0 -0
  43. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/utils.py +0 -0
  44. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire/volatilestream.py +0 -0
  45. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire.egg-info/dependency_links.txt +0 -0
  46. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire.egg-info/entry_points.txt +0 -0
  47. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire.egg-info/requires.txt +0 -0
  48. {acquire-3.17.dev1 → acquire-3.17.dev2}/acquire.egg-info/top_level.txt +0 -0
  49. {acquire-3.17.dev1 → acquire-3.17.dev2}/pyproject.toml +0 -0
  50. {acquire-3.17.dev1 → acquire-3.17.dev2}/setup.cfg +0 -0
  51. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/__init__.py +0 -0
  52. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/docs/Makefile +0 -0
  53. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/docs/conf.py +0 -0
  54. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/docs/index.rst +0 -0
  55. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_acquire_command.py +0 -0
  56. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_acquire_modules.py +0 -0
  57. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_collector.py +0 -0
  58. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_decryptor_funcs.py +0 -0
  59. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_esxi_memory.py +0 -0
  60. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_file_sorting.py +0 -0
  61. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_gui.py +0 -0
  62. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_minio_uploader.py +0 -0
  63. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_misc_users.py +0 -0
  64. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_outputs_dir.py +0 -0
  65. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_plugin.py +0 -0
  66. {acquire-3.17.dev1 → acquire-3.17.dev2}/tests/test_utils.py +0 -0
  67. {acquire-3.17.dev1 → acquire-3.17.dev2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.17.dev1
3
+ Version: 3.17.dev2
4
4
  Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -113,7 +113,7 @@ class EncryptedStream(io.RawIOBase):
113
113
  return self.fh.tell()
114
114
 
115
115
  def seek(self, pos, whence=io.SEEK_CUR):
116
- raise TypeError("seeking is not allowed")
116
+ raise io.UnsupportedOperation("seeking is not allowed")
117
117
 
118
118
  def close(self):
119
119
  self.finalize()
@@ -48,7 +48,7 @@ class ZipOutput(Output):
48
48
 
49
49
  if encrypt:
50
50
  self._fh = EncryptedStream(self.path.open("wb"), public_key)
51
- self.archive = zipfile.ZipFile(fileobj=self._fh, mode="w", compression=self.compression, allowZip64=True)
51
+ self.archive = zipfile.ZipFile(self._fh, mode="w", compression=self.compression, allowZip64=True)
52
52
  else:
53
53
  self.archive = zipfile.ZipFile(self.path, mode="w", compression=self.compression, allowZip64=True)
54
54
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
2
4
  import base64
3
5
  import contextlib
@@ -14,6 +16,9 @@ from concurrent.futures import ProcessPoolExecutor
14
16
  from datetime import datetime, timezone
15
17
  from pathlib import Path
16
18
  from queue import Empty as QueueEmptyError
19
+ from queue import Queue
20
+ from threading import Event
21
+ from typing import BinaryIO, Iterator
17
22
  from urllib import request
18
23
  from urllib.error import HTTPError
19
24
  from urllib.parse import urljoin
@@ -73,7 +78,7 @@ class VerifyError(Exception):
73
78
 
74
79
 
75
80
  class EncryptedFile(AlignedStream):
76
- def __init__(self, fh, key_file=None, key_server=None):
81
+ def __init__(self, fh: BinaryIO, key_file: Path | None = None, key_server: str | None = None) -> None:
77
82
  self.fh = fh
78
83
  self.key_file = key_file
79
84
  self.key_server = key_server
@@ -116,10 +121,10 @@ class EncryptedFile(AlignedStream):
116
121
  def seekable(self):
117
122
  return False
118
123
 
119
- def seek(self, pos, whence=io.SEEK_CUR):
124
+ def seek(self, pos: int, whence: int = io.SEEK_CUR) -> int:
120
125
  raise io.UnsupportedOperation("seeking is not allowed")
121
126
 
122
- def _read(self, offset, length):
127
+ def _read(self, offset: int, length: int) -> bytes:
123
128
  if not self.size:
124
129
  result = []
125
130
 
@@ -162,25 +167,25 @@ class EncryptedFile(AlignedStream):
162
167
  read_size = max(0, min(length, self.size - offset))
163
168
  return self.cipher.decrypt(self.fh.read(read_size))
164
169
 
165
- def chunks(self, chunk_size=CHUNK_SIZE):
170
+ def chunks(self, chunk_size: int = CHUNK_SIZE) -> Iterator[bytes]:
166
171
  while True:
167
172
  chunk = self.read(chunk_size)
168
173
  if not chunk:
169
174
  break
170
175
  yield chunk
171
176
 
172
- def verify(self):
177
+ def verify(self) -> None:
173
178
  try:
174
179
  self.cipher.verify(self.digest)
175
180
  except ValueError:
176
181
  raise VerifyError("Digest check failed")
177
182
 
178
183
  @property
179
- def file_header(self):
184
+ def file_header(self) -> c_acquire.file:
180
185
  return self._file_header
181
186
 
182
187
  @file_header.setter
183
- def file_header(self, file_header):
188
+ def file_header(self, file_header: c_acquire.file) -> None:
184
189
  if file_header.magic != FILE_MAGIC:
185
190
  raise ValueError(f"Invalid file magic: {file_header.magic}")
186
191
 
@@ -193,31 +198,31 @@ class EncryptedFile(AlignedStream):
193
198
  self._file_header = file_header
194
199
 
195
200
  @property
196
- def header(self):
201
+ def header(self) -> c_acquire.header:
197
202
  return self._header
198
203
 
199
204
  @header.setter
200
- def header(self, header):
205
+ def header(self, header: c_acquire.header) -> None:
201
206
  if header.magic != HEADER_MAGIC:
202
207
  raise ValueError(f"Invalid header magic: {header.magic}")
203
208
  self._header = header
204
209
 
205
210
  @property
206
- def footer(self):
211
+ def footer(self) -> c_acquire.footer:
207
212
  return self._footer
208
213
 
209
214
  @footer.setter
210
- def footer(self, footer):
215
+ def footer(self, footer: c_acquire.footer) -> None:
211
216
  if footer.magic != FOOTER_MAGIC:
212
217
  raise ValueError(f"Invalid footer magic: {footer}")
213
218
  self._footer = footer
214
219
 
215
220
  @property
216
- def timestamp(self):
221
+ def timestamp(self) -> datetime:
217
222
  return datetime.fromtimestamp(self.file_header.timestamp, timezone.utc)
218
223
 
219
224
 
220
- def decrypt_header(header, fingerprint, key_file=None, key_server=None):
225
+ def decrypt_header(header, fingerprint: bytes, key_file: Path | None = None, key_server: str | None = None) -> bytes:
221
226
  if not key_file and not key_server:
222
227
  raise ValueError("Need either key file or key server")
223
228
 
@@ -264,7 +269,16 @@ def check_existing(in_path: Path, out_path: Path, status_queue: multiprocessing.
264
269
  return False
265
270
 
266
271
 
267
- def worker(task_id, stop_event, status_queue, in_path, out_path, key_file=None, key_server=None, clobber=False):
272
+ def worker(
273
+ task_id: int,
274
+ stop_event: Event,
275
+ status_queue: Queue,
276
+ in_path: Path,
277
+ out_path: Path,
278
+ key_file: Path | None = None,
279
+ key_server: str | None = None,
280
+ clobber: bool = False,
281
+ ) -> None:
268
282
  success = False
269
283
  message = "An unknown error occurred"
270
284
 
@@ -325,23 +339,23 @@ def worker(task_id, stop_event, status_queue, in_path, out_path, key_file=None,
325
339
  _exit(status_queue, task_id, str(in_path), message, success)
326
340
 
327
341
 
328
- def _start(queue, task_id):
342
+ def _start(queue: Queue, task_id: int) -> None:
329
343
  queue.put_nowait((STATUS_START, task_id))
330
344
 
331
345
 
332
- def _update(queue, task_id, *args, **kwargs):
346
+ def _update(queue: Queue, task_id: int, *args, **kwargs) -> None:
333
347
  queue.put_nowait((STATUS_UPDATE, (task_id, args, kwargs)))
334
348
 
335
349
 
336
- def _info(queue, msg):
350
+ def _info(queue: Queue, msg: str) -> None:
337
351
  queue.put_nowait((STATUS_INFO, msg))
338
352
 
339
353
 
340
- def _exit(queue: multiprocessing.Queue, task_id: int, in_path: str, message: str, success: bool):
354
+ def _exit(queue: multiprocessing.Queue, task_id: int, in_path: str, message: str, success: bool) -> None:
341
355
  queue.put_nowait((STATUS_EXIT, (task_id, in_path, message, success)))
342
356
 
343
357
 
344
- def setup_logging(logger, verbosity):
358
+ def setup_logging(logger: logging.Logger, verbosity: int) -> None:
345
359
  if verbosity == 1:
346
360
  level = logging.ERROR
347
361
  elif verbosity == 2:
@@ -360,7 +374,7 @@ def setup_logging(logger, verbosity):
360
374
  logger.setLevel(level)
361
375
 
362
376
 
363
- def main():
377
+ def main() -> None:
364
378
  parser = argparse.ArgumentParser()
365
379
  parser.add_argument("files", nargs="+", type=Path, help="paths to encrypted files")
366
380
  parser.add_argument("-o", "--output", type=Path, help="optional path to output file")
@@ -476,12 +490,12 @@ def main():
476
490
  # If no successful results, return 1
477
491
  if not any(successes):
478
492
  exit_code = 1
479
- # Else, if some results were successful return 2
493
+ # Else, if some results but not all were successful return 2
480
494
  elif not all(successes):
481
495
  exit_code = 2
482
- # Else, if all were successful but there were still tasks to handle, return 2
483
- elif all(success) and tasks:
484
- exit_code = 2
496
+ # Else, if all were successful but there were still tasks to handle, return 3
497
+ elif tasks:
498
+ exit_code = 3
485
499
  exit(exit_code)
486
500
 
487
501
 
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '3.17.dev1'
16
- __version_tuple__ = version_tuple = (3, 17, 'dev1')
15
+ __version__ = version = '3.17.dev2'
16
+ __version_tuple__ = version_tuple = (3, 17, 'dev2')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.17.dev1
3
+ Version: 3.17.dev2
4
4
  Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -58,6 +58,8 @@ tests/test_outputs_tar.py
58
58
  tests/test_outputs_zip.py
59
59
  tests/test_plugin.py
60
60
  tests/test_utils.py
61
+ tests/data/private_key.pem
62
+ tests/data/public_key.pem
61
63
  tests/docs/Makefile
62
64
  tests/docs/conf.py
63
65
  tests/docs/index.rst
@@ -33,3 +33,9 @@ def mock_target(mock_fs: VirtualFilesystem) -> Target:
33
33
  target.filesystems.add(mock_fs)
34
34
  target.os = "mock"
35
35
  return target
36
+
37
+
38
+ @pytest.fixture
39
+ def public_key() -> bytes:
40
+ with open("tests/data/public_key.pem", "r") as f:
41
+ return f.read()
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDd7qXEe7cR4b0
3
+ DFmszQm5MNFPkf/uJd5i6Y4ya0prts9s4mDdS90oDUt7bvLl42i/eB+S1fw9UpmK
4
+ r8OBV51VoFPswLVSwHho7LjbGvBSgKCW31skalgOmCAiCl6BqqjI7eIqxJx3V2+G
5
+ tW19v0EvbPJNs4gWlLIzn0WZJ65HWNZxToD9NcM2uZeIFxziUFvQYv1Nzkih/HWA
6
+ 8Fw8Sdrq3JN3RG9jikJSH/MisU25IP5ehP5g66akPCufBCEc6Y2OE4wAQ7sbBMpf
7
+ /6PLZHGCVMnE8TXGBUl7m9UVwdyhvfIFBNhGilpqtSQhlqymrko7R7uZ6x5gQIz/
8
+ YzUW/Ho9AgMBAAECggEABqDTvKBeDNgxalXU4KLfS96s6lmAEo+e1+nQPu4byu/w
9
+ XrfaeFvvNuwkfXNeBzpL6K+RGoXpFMdCmk0AKy2mZytATUyc7skaDCzNeUM+QlNG
10
+ 9CFvfMT3vB71JVJcBrebxkcoHofQ6ncWOrzXkVEKoSoSRAeXe3SKtRdsi8H9teuX
11
+ uXzs8fyk+Xrp9qBE3y541HcZCh8oLypQgTFoV3cZJgcsrnRaLQUooU2n1lvl+EZx
12
+ xoZnL1LBMmX/teVICE20NJOlJN25Z+Q26tNM6ADMFmmN0hDaHUEv/tlV+MSB01Mq
13
+ nBvC1/q6pHODHW1AsLfxwT2f+VeEz4Hpbxpu1fCkXQKBgQDyhzJgVkkKctCcMlcB
14
+ 4fck+mxvQlRuWHs94RdMd28bZay5BQhd/rqicDzqIQ2NIhaPDDbUR5ElqLXQEDZD
15
+ 6s+FeHbT+iXTOtWA6qQJL0/alcEL22Nxxv098nFrjUBeinaw+PbOZq5DOT4NVL/Q
16
+ i0lGzQs+6jEH9aYc24Tu9Gi5EwKBgQDOU1KQhAea1rztLkAbEI0giKM/6vf0g6im
17
+ 1/UlUy8TjyIaE4Cwgsy/H6LuvY1KOiV/6boO3jBl5OyZZBFqIEbmEd3MH75XC0XP
18
+ bLtI00EVHU6jCf/dLE5wNhxhEuAw0KB12ecR7fZv1Wg9ltj/IR6dFBJ+Q7uuxufk
19
+ yq9R9QU5bwKBgB/Qdl5G01wIha8Ht3wqvTXfl9vccqDrAHe0kE7al/ubEdZPf7J8
20
+ 2NS4LnV0EogCAb2QF50vKi4rfHYnukachc53Z/cUqGOWIy2/GfeOekYtQN6iT+A7
21
+ /zpiFFjMdbYxKbK7ZfzbYV62IpqzFFpx+xHLkf8Vz4rAwaKldUG3VAl7AoGASlef
22
+ gk7wZoxFWri1hIr8LuLM37UMTuA5npRl0mMcrVF/miG41uDqYVtG2/sUs9ArvuE6
23
+ lyzcB3rq/YIe/DxRD4kUf/5YGQkIyGqHOQBVjQQYV4q81LaoNKpqo1enzC8AAjbX
24
+ mZBCoZ0liDuYSKVoYHThDPne4GTvHXMipMdCcKUCgYEA5h0686KZxIHgxPx7A4Br
25
+ zHKigjFGda4C9xPWBdpjvLcbFgyc+ULzP61q+h2LnE+HEuFzVqMj56dlIJl73ooI
26
+ FyjJR9ceNDyZ37XzBF3IM5AaxdvPfB/OIjMOGS5yV+1hijcGdy+RgBGVADv4lgUd
27
+ soFdGXF8UUBPdYczZR2R7jQ=
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3e6lxHu3EeG9AxZrM0J
3
+ uTDRT5H/7iXeYumOMmtKa7bPbOJg3UvdKA1Le27y5eNov3gfktX8PVKZiq/DgVed
4
+ VaBT7MC1UsB4aOy42xrwUoCglt9bJGpYDpggIgpegaqoyO3iKsScd1dvhrVtfb9B
5
+ L2zyTbOIFpSyM59FmSeuR1jWcU6A/TXDNrmXiBcc4lBb0GL9Tc5Iofx1gPBcPEna
6
+ 6tyTd0RvY4pCUh/zIrFNuSD+XoT+YOumpDwrnwQhHOmNjhOMAEO7GwTKX/+jy2Rx
7
+ glTJxPE1xgVJe5vVFcHcob3yBQTYRopaarUkIZaspq5KO0e7meseYECM/2M1Fvx6
8
+ PQIDAQAB
9
+ -----END PUBLIC KEY-----
@@ -5,6 +5,7 @@ import pytest
5
5
  from dissect.target.filesystem import VirtualFilesystem
6
6
 
7
7
  from acquire.outputs import TarOutput
8
+ from acquire.tools.decrypter import EncryptedFile
8
9
 
9
10
 
10
11
  @pytest.fixture(params=[(True, "gzip"), (True, "bzip2"), (True, "xz"), (False, None)])
@@ -41,3 +42,20 @@ def test_tar_output_write_entry(mock_fs: VirtualFilesystem, tar_output: TarOutpu
41
42
  assert file.issym()
42
43
  elif entry.is_file():
43
44
  assert file.isfile()
45
+
46
+
47
+ def test_tar_output_encrypt(mock_fs: VirtualFilesystem, public_key: bytes, tmp_path: Path) -> None:
48
+ entry_name = "/foo/bar/some-file"
49
+ entry = mock_fs.get(entry_name)
50
+ tar_output = TarOutput(tmp_path, compress=True, compression_method="gzip", encrypt=True, public_key=public_key)
51
+ tar_output.write_entry(entry_name, entry)
52
+ tar_output.close()
53
+
54
+ encrypted_stream = EncryptedFile(tar_output.path.open("rb"), Path("tests/data/private_key.pem"))
55
+ decrypted_path = tmp_path / "decrypted.tar"
56
+ # Direct streaming is not an option because tarfile needs seek when reading from encrypted files directly
57
+ with open(decrypted_path, "wb") as f:
58
+ f.write(encrypted_stream.read())
59
+
60
+ tar_file = tarfile.open(name=decrypted_path, mode="r")
61
+ assert entry.open().read() == tar_file.extractfile(entry_name).read()
@@ -6,6 +6,7 @@ import pytest
6
6
  from dissect.target.filesystem import VirtualFilesystem
7
7
 
8
8
  from acquire.outputs import ZipOutput
9
+ from acquire.tools.decrypter import EncryptedFile
9
10
 
10
11
 
11
12
  @pytest.fixture(params=[(True, "deflate"), (True, "bzip2"), (True, "lzma"), (False, None)])
@@ -45,3 +46,20 @@ def test_zip_output_write_entry(mock_fs: VirtualFilesystem, zip_output: ZipOutpu
45
46
  assert stat.S_ISLNK(file_type)
46
47
  elif entry.is_file():
47
48
  assert stat.S_ISREG(file_type)
49
+
50
+
51
+ def test_zip_output_encrypt(mock_fs: VirtualFilesystem, public_key: bytes, tmp_path: Path) -> None:
52
+ entry_name = "/foo/bar/some-file"
53
+ entry = mock_fs.get(entry_name)
54
+ zip_output = ZipOutput(tmp_path, compress=True, compression_method="bzip2", encrypt=True, public_key=public_key)
55
+ zip_output.write_entry(entry_name, entry)
56
+ zip_output.close()
57
+
58
+ encrypted_stream = EncryptedFile(zip_output.path.open("rb"), Path("tests/data/private_key.pem"))
59
+ decrypted_path = tmp_path / "decrypted.zip"
60
+ # Direct streaming is not an option because zipfile needs seek when reading from encrypted files directly
61
+ with open(decrypted_path, "wb") as f:
62
+ f.write(encrypted_stream.read())
63
+
64
+ zip_file = zipfile.ZipFile(decrypted_path, mode="r")
65
+ assert entry.open().read() == zip_file.open(entry_name).read()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes