3389da68ea507ff9bd0d12562e04cb883e9997b9
Marco Ricci Update copyright notices to...

Marco Ricci authored 2 months ago

1) # SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info>
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

2) #
Marco Ricci Update copyright notices to...

Marco Ricci authored 2 months ago

3) # SPDX-License-Identifier: Zlib
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

4) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

5) """Exporter for the vault "storeroom" configuration format.
6) 
7) The "storeroom" format is the experimental format used in alpha and beta
8) versions of vault beyond v0.3.0.  The configuration is stored as
9) a separate directory, which acts like a hash table (i.e. has named
10) slots) and provides an impure quasi-filesystem interface.  Each hash
11) table entry is separately encrypted and authenticated.  James Coglan
12) designed this format to avoid concurrent write issues when updating or
13) synchronizing the vault configuration with e.g. a cloud service.
14) 
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

15) The public interface is the [`export_storeroom_data`][] function.
16) Multiple *non-public* functions are additionally documented here for
17) didactical and educational reasons, but they are not part of the module
18) API, are subject to change without notice (including removal), and
19) should *not* be used or relied on.
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

20) 
21) """
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

22) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

23) # ruff: noqa: S303
24) 
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

25) from __future__ import annotations
26) 
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

27) import base64
Marco Ricci Add support for Python 3.9

Marco Ricci authored 5 months ago

28) import fnmatch
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

29) import importlib
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

30) import json
31) import logging
32) import os
33) import os.path
34) import struct
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

35) from typing import TYPE_CHECKING, Any
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

36) 
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

37) from derivepassphrase import _cli_msg as _msg
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

38) from derivepassphrase import _types, exporter
Marco Ricci Move vault key and path det...

Marco Ricci authored 6 months ago

39) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

40) if TYPE_CHECKING:
41)     from collections.abc import Iterator
42) 
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

43)     from cryptography.hazmat.primitives import ciphers, hashes, hmac, padding
44)     from cryptography.hazmat.primitives.ciphers import algorithms, modes
45)     from cryptography.hazmat.primitives.kdf import pbkdf2
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

46)     from typing_extensions import Buffer
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

47) else:
48)     try:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

49)         importlib.import_module('cryptography')
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

50)     except ModuleNotFoundError as exc:
51) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

52)         class _DummyModule:  # pragma: no cover
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

53)             def __init__(self, exc: type[Exception]) -> None:
54)                 self.exc = exc
55) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

56)             def __getattr__(self, name: str) -> Any:  # noqa: ANN401
57)                 def func(*args: Any, **kwargs: Any) -> Any:  # noqa: ANN401,ARG001
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

58)                     raise self.exc
59) 
60)                 return func
61) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

62)         ciphers = hashes = hmac = padding = _DummyModule(exc)
63)         algorithms = modes = pbkdf2 = _DummyModule(exc)
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

64)         STUBBED = True
65)     else:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

66)         from cryptography.hazmat.primitives import (
67)             ciphers,
68)             hashes,
69)             hmac,
70)             padding,
71)         )
72)         from cryptography.hazmat.primitives.ciphers import algorithms, modes
73)         from cryptography.hazmat.primitives.kdf import pbkdf2
74) 
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 6 months ago

75)         STUBBED = False
76) 
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

77) STOREROOM_MASTER_KEYS_UUID = b'35b7c7ed-f71e-4adf-9051-02fb0f1e0e17'
78) VAULT_CIPHER_UUID = b'73e69e8a-cb05-4b50-9f42-59d76a511299'
79) IV_SIZE = 16
80) KEY_SIZE = MAC_SIZE = 32
81) ENCRYPTED_KEYPAIR_SIZE = 128
82) VERSION_SIZE = 1
83) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

84) __all__ = ('export_storeroom_data',)
85) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

86) logger = logging.getLogger(__name__)
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

87) 
88) 
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

89) def _h(bs: Buffer) -> str:
90)     return '<{}>'.format(memoryview(bs).hex(' '))
91) 
92) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

93) def derive_master_keys_keys(
94)     password: str | Buffer,
95)     iterations: int,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

96) ) -> _types.StoreroomKeyPair:
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

97)     """Derive encryption and signing keys for the master keys data.
98) 
99)     The master password is run through a key derivation function to
100)     obtain a 64-byte string, which is then split to yield two 32-byte
101)     keys.  The key derivation function is PBKDF2, using HMAC-SHA1 and
102)     salted with the storeroom master keys UUID.
103) 
104)     Args:
105)         password:
106)             A master password for the storeroom instance.  Usually read
107)             from the `VAULT_KEY` environment variable, otherwise
108)             defaults to the username.
109)         iterations:
110)             A count of rounds for the underlying key derivation
111)             function.  Usually stored as a setting next to the encrypted
112)             master keys data.
113) 
114)     Returns:
115)         A 2-tuple of keys, the encryption key and the signing key, to
116)         decrypt and verify the master keys data with.
117) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

118)     Warning:
119)         Non-public function, provided for didactical and educational
120)         purposes only.  Subject to change without notice, including
121)         removal.
122) 
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

123)     """
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

124)     if isinstance(password, str):
125)         password = password.encode('ASCII')
126)     master_keys_keys_blob = pbkdf2.PBKDF2HMAC(
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

127)         algorithm=hashes.SHA1(),
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

128)         length=2 * KEY_SIZE,
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

129)         salt=STOREROOM_MASTER_KEYS_UUID,
130)         iterations=iterations,
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

131)     ).derive(bytes(password))
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

132)     encryption_key, signing_key = struct.unpack(
133)         f'{KEY_SIZE}s {KEY_SIZE}s', master_keys_keys_blob
134)     )
135)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

136)         _msg.TranslatedString(
137)             _msg.DebugMsgTemplate.DERIVED_MASTER_KEYS_KEYS,
138)             enc_key=_h(encryption_key),
139)             sign_key=_h(signing_key),
140)             pw_bytes=_h(password),
141)             algorithm='SHA256',
142)             length=64,
143)             salt=STOREROOM_MASTER_KEYS_UUID,
144)             iterations=iterations,
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

145)         ),
146)     )
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

147)     return _types.StoreroomKeyPair(
148)         encryption_key=encryption_key,
149)         signing_key=signing_key,
150)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

151) 
152) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

153) def decrypt_master_keys_data(
154)     data: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

155)     keys: _types.StoreroomKeyPair,
156) ) -> _types.StoreroomMasterKeys:
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

157)     r"""Decrypt the master keys data.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

158) 
159)     The master keys data contains:
160) 
161)     - a 16-byte IV,
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

162)     - a 96-byte AES256-CBC-encrypted payload, plus 16 further bytes of
163)       PKCS7 padding, and
164)     - a 32-byte MAC of the preceding 128 bytes.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

165) 
166)     The decrypted payload itself consists of three 32-byte keys: the
167)     hashing, encryption and signing keys, in that order.
168) 
169)     The encrypted payload is encrypted with the encryption key, and the
170)     MAC is created based on the signing key.  As per standard
171)     cryptographic procedure, the MAC can be verified before attempting
172)     to decrypt the payload.
173) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

174)     Because the payload size is both fixed and a multiple of the cipher
175)     blocksize, in this case, the PKCS7 padding always is `b'\x10' * 16`.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

176) 
177)     Args:
178)         data:
179)             The encrypted master keys data.
180)         keys:
181)             The encryption and signing keys for the master keys data.
182)             These should have previously been derived via the
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

183)             [`derive_master_keys_keys`][] function.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

184) 
185)     Returns:
186)         The master encryption, signing and hashing keys.
187) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

188)     Raises:
189)         cryptography.exceptions.InvalidSignature:
190)             The data does not contain a valid signature under the given
191)             key.
192)         ValueError:
193)             The format is invalid, in a non-cryptographic way.  (For
194)             example, it contains an unsupported version marker, or
195)             unexpected extra contents, or invalid padding.)
196) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

197)     Warning:
198)         Non-public function, provided for didactical and educational
199)         purposes only.  Subject to change without notice, including
200)         removal.
201) 
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

202)     """
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

203)     data = memoryview(data).toreadonly().cast('c')
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

204)     ciphertext, claimed_mac = struct.unpack(
205)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
206)     )
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

207)     actual_mac = hmac.HMAC(keys.signing_key, hashes.SHA256())
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

208)     actual_mac.update(ciphertext)
209)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

210)         _msg.TranslatedString(
211)             _msg.DebugMsgTemplate.MASTER_KEYS_DATA_MAC_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

212)             sign_key=_h(keys.signing_key),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

213)             ciphertext=_h(ciphertext),
214)             claimed_mac=_h(claimed_mac),
215)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

216)         ),
217)     )
218)     actual_mac.verify(claimed_mac)
219) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

220)     try:
221)         iv, payload = struct.unpack(
222)             f'{IV_SIZE}s {len(ciphertext) - IV_SIZE}s', ciphertext
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

223)         )
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

224)         decryptor = ciphers.Cipher(
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

225)             algorithms.AES256(keys.encryption_key), modes.CBC(iv)
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

226)         ).decryptor()
227)         padded_plaintext = bytearray()
228)         padded_plaintext.extend(decryptor.update(payload))
229)         padded_plaintext.extend(decryptor.finalize())
230)         unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
231)         plaintext = bytearray()
232)         plaintext.extend(unpadder.update(padded_plaintext))
233)         plaintext.extend(unpadder.finalize())
234)         hashing_key, encryption_key, signing_key = struct.unpack(
235)             f'{KEY_SIZE}s {KEY_SIZE}s {KEY_SIZE}s', plaintext
236)         )
237)     except (ValueError, struct.error) as exc:
238)         msg = 'Invalid encrypted master keys payload'
239)         raise ValueError(msg) from exc
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

240)     return _types.StoreroomMasterKeys(
241)         hashing_key=hashing_key,
242)         encryption_key=encryption_key,
243)         signing_key=signing_key,
244)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

245) 
246) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

247) def decrypt_session_keys(
248)     data: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

249)     master_keys: _types.StoreroomMasterKeys,
250) ) -> _types.StoreroomKeyPair:
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

251)     r"""Decrypt the bucket item's session keys.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

252) 
253)     The bucket item's session keys are single-use keys for encrypting
254)     and signing a single item in the storage bucket.  The encrypted
255)     session key data consists of:
256) 
257)     - a 16-byte IV,
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

258)     - a 64-byte AES256-CBC-encrypted payload, plus 16 further bytes of
259)       PKCS7 padding, and
260)     - a 32-byte MAC of the preceding 96 bytes.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

261) 
262)     The encrypted payload is encrypted with the master encryption key,
263)     and the MAC is created with the master signing key.  As per standard
264)     cryptographic procedure, the MAC can be verified before attempting
265)     to decrypt the payload.
266) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

267)     Because the payload size is both fixed and a multiple of the cipher
268)     blocksize, in this case, the PKCS7 padding always is `b'\x10' * 16`.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

269) 
270)     Args:
271)         data:
272)             The encrypted bucket item session key data.
273)         master_keys:
274)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

275)             obtained via the [`decrypt_master_keys_data`][] function.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

276) 
277)     Returns:
278)         The bucket item's encryption and signing keys.
279) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

280)     Raises:
281)         cryptography.exceptions.InvalidSignature:
282)             The data does not contain a valid signature under the given
283)             key.
284)         ValueError:
285)             The format is invalid, in a non-cryptographic way.  (For
286)             example, it contains an unsupported version marker, or
287)             unexpected extra contents, or invalid padding.)
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

288) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

289)     Warning:
290)         Non-public function, provided for didactical and educational
291)         purposes only.  Subject to change without notice, including
292)         removal.
293) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

294)     """
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

295)     data = memoryview(data).toreadonly().cast('c')
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

296)     ciphertext, claimed_mac = struct.unpack(
297)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
298)     )
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

299)     actual_mac = hmac.HMAC(master_keys.signing_key, hashes.SHA256())
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

300)     actual_mac.update(ciphertext)
301)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

302)         _msg.TranslatedString(
303)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_MAC_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

304)             sign_key=_h(master_keys.signing_key),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

305)             ciphertext=_h(ciphertext),
306)             claimed_mac=_h(claimed_mac),
307)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

308)         ),
309)     )
310)     actual_mac.verify(claimed_mac)
311) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

312)     try:
313)         iv, payload = struct.unpack(
314)             f'{IV_SIZE}s {len(ciphertext) - IV_SIZE}s', ciphertext
315)         )
316)         decryptor = ciphers.Cipher(
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

317)             algorithms.AES256(master_keys.encryption_key), modes.CBC(iv)
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

318)         ).decryptor()
319)         padded_plaintext = bytearray()
320)         padded_plaintext.extend(decryptor.update(payload))
321)         padded_plaintext.extend(decryptor.finalize())
322)         unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
323)         plaintext = bytearray()
324)         plaintext.extend(unpadder.update(padded_plaintext))
325)         plaintext.extend(unpadder.finalize())
326)         session_encryption_key, session_signing_key = struct.unpack(
327)             f'{KEY_SIZE}s {KEY_SIZE}s', plaintext
328)         )
329)     except (ValueError, struct.error) as exc:
330)         msg = 'Invalid encrypted session keys payload'
331)         raise ValueError(msg) from exc
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

332) 
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

333)     session_keys = _types.StoreroomKeyPair(
334)         encryption_key=session_encryption_key,
335)         signing_key=session_signing_key,
336)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

337) 
338)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

339)         _msg.TranslatedString(
340)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

341)             enc_key=_h(master_keys.encryption_key),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

342)             iv=_h(iv),
343)             ciphertext=_h(payload),
344)             plaintext=_h(plaintext),
345)             code=_msg.TranslatedString(
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

346)                 'StoreroomKeyPair(encryption_key=bytes.fromhex({enc_key!r}), '
347)                 'signing_key=bytes.fromhex({sign_key!r}))',
348)                 enc_key=session_keys.encryption_key.hex(' '),
349)                 sign_key=session_keys.signing_key.hex(' '),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

350)             ),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

351)         ),
352)     )
353) 
354)     return session_keys
355) 
356) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

357) def decrypt_contents(
358)     data: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

359)     session_keys: _types.StoreroomKeyPair,
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

360) ) -> Buffer:
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

361)     """Decrypt the bucket item's contents.
362) 
363)     The data consists of:
364) 
365)     - a 16-byte IV,
366)     - a variable-sized AES256-CBC-encrypted payload (using PKCS7 padding
367)       on the inside), and
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

368)     - a 32-byte MAC of the preceding bytes.
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

369) 
370)     The encrypted payload is encrypted with the bucket item's session
371)     encryption key, and the MAC is created with the bucket item's
372)     session signing key.  As per standard cryptographic procedure, the
373)     MAC can be verified before attempting to decrypt the payload.
374) 
375)     Args:
376)         data:
377)             The encrypted bucket item payload data.
378)         session_keys:
379)             The bucket item's session keys.  Presumably these have
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

380)             previously been obtained via the [`decrypt_session_keys`][]
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

381)             function.
382) 
383)     Returns:
384)         The bucket item's payload.
385) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

386)     Raises:
387)         cryptography.exceptions.InvalidSignature:
388)             The data does not contain a valid signature under the given
389)             key.
390)         ValueError:
391)             The format is invalid, in a non-cryptographic way.  (For
392)             example, it contains an unsupported version marker, or
393)             unexpected extra contents, or invalid padding.)
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

394) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

395)     Warning:
396)         Non-public function, provided for didactical and educational
397)         purposes only.  Subject to change without notice, including
398)         removal.
399) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

400)     """
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

401)     data = memoryview(data).toreadonly().cast('c')
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

402)     ciphertext, claimed_mac = struct.unpack(
403)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
404)     )
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

405)     actual_mac = hmac.HMAC(session_keys.signing_key, hashes.SHA256())
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

406)     actual_mac.update(ciphertext)
407)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

408)         _msg.TranslatedString(
409)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_MAC_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

410)             sign_key=_h(session_keys.signing_key),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

411)             ciphertext=_h(ciphertext),
412)             claimed_mac=_h(claimed_mac),
413)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

414)         ),
415)     )
416)     actual_mac.verify(claimed_mac)
417) 
418)     iv, payload = struct.unpack(
419)         f'{IV_SIZE}s {len(ciphertext) - IV_SIZE}s', ciphertext
420)     )
421)     decryptor = ciphers.Cipher(
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

422)         algorithms.AES256(session_keys.encryption_key), modes.CBC(iv)
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

423)     ).decryptor()
424)     padded_plaintext = bytearray()
425)     padded_plaintext.extend(decryptor.update(payload))
426)     padded_plaintext.extend(decryptor.finalize())
427)     unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
428)     plaintext = bytearray()
429)     plaintext.extend(unpadder.update(padded_plaintext))
430)     plaintext.extend(unpadder.finalize())
431) 
432)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

433)         _msg.TranslatedString(
434)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

435)             enc_key=_h(session_keys.encryption_key),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

436)             iv=_h(iv),
437)             ciphertext=_h(payload),
438)             plaintext=_h(plaintext),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

439)         ),
440)     )
441) 
442)     return plaintext
443) 
444) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

445) def decrypt_bucket_item(
446)     bucket_item: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

447)     master_keys: _types.StoreroomMasterKeys,
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

448) ) -> Buffer:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

449)     """Decrypt a bucket item.
450) 
451)     Args:
452)         bucket_item:
453)             The encrypted bucket item.
454)         master_keys:
455)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

456)             obtained via the [`decrypt_master_keys_data`][] function.
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

457) 
458)     Returns:
459)         The decrypted bucket item.
460) 
461)     Raises:
462)         cryptography.exceptions.InvalidSignature:
463)             The data does not contain a valid signature under the given
464)             key.
465)         ValueError:
466)             The format is invalid, in a non-cryptographic way.  (For
467)             example, it contains an unsupported version marker, or
468)             unexpected extra contents, or invalid padding.)
469) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

470)     Warning:
471)         Non-public function, provided for didactical and educational
472)         purposes only.  Subject to change without notice, including
473)         removal.
474) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

475)     """
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

476)     bucket_item = memoryview(bucket_item).toreadonly().cast('c')
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

477)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

478)         _msg.TranslatedString(
479)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_KEY_INFO,
480)             plaintext=_h(bucket_item),
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

481)             enc_key=_h(master_keys.encryption_key),
482)             sign_key=_h(master_keys.signing_key),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

483)         ),
484)     )
485)     data_version, encrypted_session_keys, data_contents = struct.unpack(
486)         (
487)             f'B {ENCRYPTED_KEYPAIR_SIZE}s '
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

488)             f'{len(bucket_item) - 1 - ENCRYPTED_KEYPAIR_SIZE}s'
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

489)         ),
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

490)         bucket_item,
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

491)     )
492)     if data_version != 1:
493)         msg = f'Cannot handle version {data_version} encrypted data'
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

494)         raise ValueError(msg)
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

495)     session_keys = decrypt_session_keys(encrypted_session_keys, master_keys)
496)     return decrypt_contents(data_contents, session_keys)
497) 
498) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

499) def decrypt_bucket_file(
Marco Ricci Support exports from outsid...

Marco Ricci authored 6 months ago

500)     filename: str,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 2 months ago

501)     master_keys: _types.StoreroomMasterKeys,
Marco Ricci Support exports from outsid...

Marco Ricci authored 6 months ago

502)     *,
503)     root_dir: str | bytes | os.PathLike = '.',
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

504) ) -> Iterator[Buffer]:
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

505)     """Decrypt a complete bucket.
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

506) 
507)     Args:
508)         filename:
509)             The bucket file's filename.
510)         master_keys:
511)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

512)             obtained via the [`decrypt_master_keys_data`][] function.
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

513)         root_dir:
514)             The root directory of the data store.  The filename is
515)             interpreted relatively to this directory.
516) 
517)     Yields:
Marco Ricci Convert old syntax for Yiel...

Marco Ricci authored 5 months ago

518)         A decrypted bucket item.
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

519) 
520)     Raises:
521)         cryptography.exceptions.InvalidSignature:
522)             The data does not contain a valid signature under the given
523)             key.
524)         ValueError:
525)             The format is invalid, in a non-cryptographic way.  (For
526)             example, it contains an unsupported version marker, or
527)             unexpected extra contents, or invalid padding.)
528) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

529)     Warning:
530)         Non-public function, provided for didactical and educational
531)         purposes only.  Subject to change without notice, including
532)         removal.
533) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

534)     """
Marco Ricci Support exports from outsid...

Marco Ricci authored 6 months ago

535)     with open(
536)         os.path.join(os.fsdecode(root_dir), filename), 'rb'
537)     ) as bucket_file:
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

538)         header_line = bucket_file.readline()
539)         try:
540)             header = json.loads(header_line)
541)         except ValueError as exc:
542)             msg = f'Invalid bucket file: {filename}'
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

543)             raise ValueError(msg) from exc
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

544)         if header != {'version': 1}:
545)             msg = f'Invalid bucket file: {filename}'
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

546)             raise ValueError(msg) from None
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

547)         for line in bucket_file:
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

548)             yield decrypt_bucket_item(
549)                 base64.standard_b64decode(line), master_keys
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

550)             )
551) 
552) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

553) def _store(config: dict[str, Any], path: str, json_contents: bytes) -> None:
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

554)     """Store the JSON contents at path in the config structure.
555) 
556)     Traverse the config structure according to path, and set the value
557)     of the leaf to the decoded JSON contents.
558) 
559)     A path `/foo/bar/xyz` translates to the JSON structure
560)     `{"foo": {"bar": {"xyz": ...}}}`.
561) 
562)     Args:
563)         config:
564)             The (top-level) configuration structure to update.
565)         path:
566)             The path within the configuration structure to traverse.
567)         json_contents:
568)             The contents to set the item to, after JSON-decoding.
569) 
570)     Raises:
571)         json.JSONDecodeError:
572)             There was an error parsing the JSON contents.
573) 
574)     """
575)     contents = json.loads(json_contents)
576)     path_parts = [part for part in path.split('/') if part]
577)     for part in path_parts[:-1]:
578)         config = config.setdefault(part, {})
579)     if path_parts:
580)         config[path_parts[-1]] = contents
581) 
582) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

583) @exporter.register_export_vault_config_data_handler('storeroom')
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

584) def export_storeroom_data(  # noqa: C901,PLR0912,PLR0914,PLR0915
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

585)     path: str | bytes | os.PathLike | None = None,
586)     key: str | Buffer | None = None,
587)     *,
588)     format: str = 'storeroom',  # noqa: A002
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

589) ) -> dict[str, Any]:
590)     """Export the full configuration stored in the storeroom.
591) 
592)     Args:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

593)         path:
594)             The path to the vault configuration directory.  If not
595)             given, then query [`exporter.get_vault_path`][] for the
596)             correct value.
597)         key:
598)             Encryption key/password for the (master keys file in the)
599)             configuration directory, usually the username, or passed via
600)             the `VAULT_KEY` environment variable.  If not given, then
601)             query [`exporter.get_vault_key`][] for the value.
602)         format:
603)             The format to attempt parsing as.  If specified, must be
604)             `storeroom`.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

605) 
606)     Returns:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

607)         The vault configuration, as recorded in the configuration
608)         directory.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

609) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

610)         This may or may not be a valid configuration according to
611)         `vault` or `derivepassphrase`.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

612) 
613)     Raises:
614)         RuntimeError:
615)             Something went wrong during data collection, e.g. we
616)             encountered unsupported or corrupted data in the storeroom.
617)         json.JSONDecodeError:
618)             An internal JSON data structure failed to parse from disk.
619)             The storeroom is probably corrupted.
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

620)         exporter.NotAVaultConfigError:
621)             The directory does contain not a storeroom.
622)         ValueError:
623)             The requested format is invalid.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

624) 
625)     """
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

626)     # Trigger import errors if necessary.
627)     importlib.import_module('cryptography')
628)     if path is None:
629)         path = exporter.get_vault_path()
630)     if key is None:
631)         key = exporter.get_vault_key()
632)     if format != 'storeroom':  # pragma: no cover
633)         msg = exporter.INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT.format(
634)             fmt=format
635)         )
636)         raise ValueError(msg)
637)     try:
638)         master_keys_file = open(  # noqa: SIM115
639)             os.path.join(os.fsdecode(path), '.keys'),
640)             encoding='utf-8',
641)         )
642)     except FileNotFoundError as exc:
643)         raise exporter.NotAVaultConfigError(
644)             os.fsdecode(path),
645)             format='storeroom',
646)         ) from exc
647)     with master_keys_file:
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

648)         header = json.loads(master_keys_file.readline())
649)         if header != {'version': 1}:
650)             msg = 'bad or unsupported keys version header'
651)             raise RuntimeError(msg)
652)         raw_keys_data = base64.standard_b64decode(master_keys_file.readline())
653)         encrypted_keys_params, encrypted_keys = struct.unpack(
654)             f'B {len(raw_keys_data) - 1}s', raw_keys_data
655)         )
656)         if master_keys_file.read():
657)             msg = 'trailing data; cannot make sense of .keys file'
658)             raise RuntimeError(msg)
659)     encrypted_keys_version = encrypted_keys_params >> 4
660)     if encrypted_keys_version != 1:
661)         msg = f'cannot handle version {encrypted_keys_version} encrypted keys'
662)         raise RuntimeError(msg)
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

663)     logger.info(
664)         _msg.TranslatedString(_msg.InfoMsgTemplate.PARSING_MASTER_KEYS_DATA)
665)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

666)     encrypted_keys_iterations = 2 ** (10 + (encrypted_keys_params & 0x0F))
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

667)     master_keys_keys = derive_master_keys_keys(key, encrypted_keys_iterations)
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

668)     master_keys = decrypt_master_keys_data(encrypted_keys, master_keys_keys)
669) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

670)     config_structure: dict[str, Any] = {}
671)     json_contents: dict[str, bytes] = {}
Marco Ricci Add support for Python 3.9

Marco Ricci authored 5 months ago

672)     # Use glob.glob(..., root_dir=...) here once Python 3.9 becomes
673)     # unsupported.
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

674)     storeroom_path_str = os.fsdecode(path)
Marco Ricci Add support for Python 3.9

Marco Ricci authored 5 months ago

675)     valid_hashdirs = [
676)         hashdir_name
677)         for hashdir_name in os.listdir(storeroom_path_str)
678)         if fnmatch.fnmatch(hashdir_name, '[01][0-9a-f]')
679)     ]
680)     for file in valid_hashdirs:
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

681)         logger.info(
682)             _msg.TranslatedString(
683)                 _msg.InfoMsgTemplate.DECRYPTING_BUCKET,
684)                 bucket_number=file,
685)             )
686)         )
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

687)         bucket_contents = [
688)             bytes(item)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

689)             for item in decrypt_bucket_file(file, master_keys, root_dir=path)
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

690)         ]
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

691)         bucket_index = json.loads(bucket_contents.pop(0))
692)         for pos, item in enumerate(bucket_index):
693)             json_contents[item] = bucket_contents[pos]
694)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

695)                 _msg.TranslatedString(
696)                     _msg.DebugMsgTemplate.BUCKET_ITEM_FOUND,
697)                     path=item,
698)                     value=bucket_contents[pos],
699)                 )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

700)             )
701)     dirs_to_check: dict[str, list[str]] = {}
702)     json_payload: Any
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

703)     logger.info(
704)         _msg.TranslatedString(_msg.InfoMsgTemplate.ASSEMBLING_CONFIG_STRUCTURE)
705)     )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

706)     for item_path, json_content in sorted(json_contents.items()):
707)         if item_path.endswith('/'):
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

708)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

709)                 _msg.TranslatedString(
710)                     _msg.DebugMsgTemplate.POSTPONING_DIRECTORY_CONTENTS_CHECK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

711)                     path=item_path,
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

712)                     contents=json_content.decode('utf-8'),
713)                 )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

714)             )
715)             json_payload = json.loads(json_content)
716)             if not isinstance(json_payload, list) or any(
717)                 not isinstance(x, str) for x in json_payload
718)             ):
719)                 msg = (
720)                     f'Directory index is not actually an index: '
721)                     f'{json_content!r}'
722)                 )
723)                 raise RuntimeError(msg)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

724)             dirs_to_check[item_path] = json_payload
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

725)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

726)                 _msg.TranslatedString(
727)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS_EMPTY_DIRECTORY,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

728)                     path=item_path,
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

729)                 ),
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

730)             )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

731)             _store(config_structure, item_path, b'{}')
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

732)         else:
733)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

734)                 _msg.TranslatedString(
735)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

736)                     path=item_path,
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

737)                     value=json_content.decode('utf-8'),
738)                 ),
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

739)             )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

740)             _store(config_structure, item_path, json_content)
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

741)     logger.info(
742)         _msg.TranslatedString(
743)             _msg.InfoMsgTemplate.CHECKING_CONFIG_STRUCTURE_CONSISTENCY,
744)         )
745)     )
Marco Ricci Emit new info messages and...

Marco Ricci authored 3 months ago

746)     # Sorted order is important; see `maybe_obj` below.
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

747)     for dir_, namelist_ in sorted(dirs_to_check.items()):
748)         namelist = [x.rstrip('/') for x in namelist_]
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

749)         obj: dict[Any, Any] = config_structure
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

750)         for part in dir_.split('/'):
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

751)             if part:
752)                 # Because we iterate paths in sorted order, parent
753)                 # directories are encountered before child directories.
754)                 # So parent directories always exist (lest we would have
755)                 # aborted earlier).
756)                 #
757)                 # Of course, the type checker doesn't necessarily know
758)                 # this, so we need to use assertions anyway.
759)                 maybe_obj = obj.get(part)
Marco Ricci Update ruff to v0.8.x, refo...

Marco Ricci authored 2 months ago

760)                 assert isinstance(maybe_obj, dict), (
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

761)                     f'Cannot traverse storage path {dir_!r}'
Marco Ricci Update ruff to v0.8.x, refo...

Marco Ricci authored 2 months ago

762)                 )
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

763)                 obj = maybe_obj
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

764)         if set(obj.keys()) != set(namelist):
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

765)             msg = f'Object key mismatch for path {dir_!r}'
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

766)             raise RuntimeError(msg)
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

767)         logger.debug(
768)             _msg.TranslatedString(
769)                 _msg.DebugMsgTemplate.DIRECTORY_CONTENTS_CHECK_OK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

770)                 path=dir_,
771)                 contents=json.dumps(namelist_),
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

772)             )
773)         )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

774)     return config_structure
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

775) 
776) 
777) if __name__ == '__main__':
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

778)     logging.basicConfig(level=('DEBUG' if os.getenv('DEBUG') else 'WARNING'))
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

779)     config_structure = export_storeroom_data(format='storeroom')