0ecb8a91d39b699816e21d6e215878ddc50252e3
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 Add an actual storeroom exp...

Marco Ricci authored 7 months ago

35) from typing import TYPE_CHECKING, Any, TypedDict
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 vault key and path det...

Marco Ricci authored 6 months ago

38) from derivepassphrase import exporter
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 Add prototype for "storeroo...

Marco Ricci authored 7 months ago

93) class KeyPair(TypedDict):
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

94)     """A pair of AES256 keys, one for encryption and one for signing.
95) 
96)     Attributes:
97)         encryption_key:
98)             AES256 key, used for encryption with AES256-CBC (with PKCS#7
99)             padding).
100)         signing_key:
101)             AES256 key, used for signing with HMAC-SHA256.
102) 
103)     """
104) 
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

105)     encryption_key: bytes
Marco Ricci Enable cross-references on...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

107)     signing_key: bytes
Marco Ricci Enable cross-references on...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

109) 
110) 
111) class MasterKeys(TypedDict):
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

112)     """A triple of AES256 keys, for encryption, signing and hashing.
113) 
114)     Attributes:
115)         hashing_key:
116)             AES256 key, used for hashing with HMAC-SHA256 to derive
117)             a hash table slot for an item.
118)         encryption_key:
119)             AES256 key, used for encryption with AES256-CBC (with PKCS#7
120)             padding).
121)         signing_key:
122)             AES256 key, used for signing with HMAC-SHA256.
123) 
124)     """
125) 
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

126)     hashing_key: bytes
Marco Ricci Enable cross-references on...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

128)     encryption_key: bytes
Marco Ricci Enable cross-references on...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

130)     signing_key: bytes
Marco Ricci Enable cross-references on...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

132) 
133) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

134) def derive_master_keys_keys(
135)     password: str | Buffer,
136)     iterations: int,
137) ) -> KeyPair:
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

138)     """Derive encryption and signing keys for the master keys data.
139) 
140)     The master password is run through a key derivation function to
141)     obtain a 64-byte string, which is then split to yield two 32-byte
142)     keys.  The key derivation function is PBKDF2, using HMAC-SHA1 and
143)     salted with the storeroom master keys UUID.
144) 
145)     Args:
146)         password:
147)             A master password for the storeroom instance.  Usually read
148)             from the `VAULT_KEY` environment variable, otherwise
149)             defaults to the username.
150)         iterations:
151)             A count of rounds for the underlying key derivation
152)             function.  Usually stored as a setting next to the encrypted
153)             master keys data.
154) 
155)     Returns:
156)         A 2-tuple of keys, the encryption key and the signing key, to
157)         decrypt and verify the master keys data with.
158) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

159)     Warning:
160)         Non-public function, provided for didactical and educational
161)         purposes only.  Subject to change without notice, including
162)         removal.
163) 
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

170)         salt=STOREROOM_MASTER_KEYS_UUID,
171)         iterations=iterations,
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

173)     encryption_key, signing_key = struct.unpack(
174)         f'{KEY_SIZE}s {KEY_SIZE}s', master_keys_keys_blob
175)     )
176)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

177)         _msg.TranslatedString(
178)             _msg.DebugMsgTemplate.DERIVED_MASTER_KEYS_KEYS,
179)             enc_key=_h(encryption_key),
180)             sign_key=_h(signing_key),
181)             pw_bytes=_h(password),
182)             algorithm='SHA256',
183)             length=64,
184)             salt=STOREROOM_MASTER_KEYS_UUID,
185)             iterations=iterations,
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

186)         ),
187)     )
188)     return {
189)         'encryption_key': encryption_key,
190)         'signing_key': signing_key,
191)     }
192) 
193) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

194) def decrypt_master_keys_data(
195)     data: Buffer,
196)     keys: KeyPair,
197) ) -> MasterKeys:
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

199) 
200)     The master keys data contains:
201) 
202)     - a 16-byte IV,
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

206) 
207)     The decrypted payload itself consists of three 32-byte keys: the
208)     hashing, encryption and signing keys, in that order.
209) 
210)     The encrypted payload is encrypted with the encryption key, and the
211)     MAC is created based on the signing key.  As per standard
212)     cryptographic procedure, the MAC can be verified before attempting
213)     to decrypt the payload.
214) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

215)     Because the payload size is both fixed and a multiple of the cipher
216)     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

217) 
218)     Args:
219)         data:
220)             The encrypted master keys data.
221)         keys:
222)             The encryption and signing keys for the master keys data.
223)             These should have previously been derived via the
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

225) 
226)     Returns:
227)         The master encryption, signing and hashing keys.
228) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

229)     Raises:
230)         cryptography.exceptions.InvalidSignature:
231)             The data does not contain a valid signature under the given
232)             key.
233)         ValueError:
234)             The format is invalid, in a non-cryptographic way.  (For
235)             example, it contains an unsupported version marker, or
236)             unexpected extra contents, or invalid padding.)
237) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

238)     Warning:
239)         Non-public function, provided for didactical and educational
240)         purposes only.  Subject to change without notice, including
241)         removal.
242) 
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

245)     ciphertext, claimed_mac = struct.unpack(
246)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
247)     )
248)     actual_mac = hmac.HMAC(keys['signing_key'], hashes.SHA256())
249)     actual_mac.update(ciphertext)
250)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

251)         _msg.TranslatedString(
252)             _msg.DebugMsgTemplate.MASTER_KEYS_DATA_MAC_INFO,
253)             sign_key=_h(keys['signing_key']),
254)             ciphertext=_h(ciphertext),
255)             claimed_mac=_h(claimed_mac),
256)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

257)         ),
258)     )
259)     actual_mac.verify(claimed_mac)
260) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

265)         decryptor = ciphers.Cipher(
266)             algorithms.AES256(keys['encryption_key']), modes.CBC(iv)
267)         ).decryptor()
268)         padded_plaintext = bytearray()
269)         padded_plaintext.extend(decryptor.update(payload))
270)         padded_plaintext.extend(decryptor.finalize())
271)         unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
272)         plaintext = bytearray()
273)         plaintext.extend(unpadder.update(padded_plaintext))
274)         plaintext.extend(unpadder.finalize())
275)         hashing_key, encryption_key, signing_key = struct.unpack(
276)             f'{KEY_SIZE}s {KEY_SIZE}s {KEY_SIZE}s', plaintext
277)         )
278)     except (ValueError, struct.error) as exc:
279)         msg = 'Invalid encrypted master keys payload'
280)         raise ValueError(msg) from exc
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

281)     return {
282)         'hashing_key': hashing_key,
283)         'encryption_key': encryption_key,
284)         'signing_key': signing_key,
285)     }
286) 
287) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

288) def decrypt_session_keys(
289)     data: Buffer,
290)     master_keys: MasterKeys,
291) ) -> KeyPair:
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

293) 
294)     The bucket item's session keys are single-use keys for encrypting
295)     and signing a single item in the storage bucket.  The encrypted
296)     session key data consists of:
297) 
298)     - a 16-byte IV,
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

302) 
303)     The encrypted payload is encrypted with the master encryption key,
304)     and the MAC is created with the master signing key.  As per standard
305)     cryptographic procedure, the MAC can be verified before attempting
306)     to decrypt the payload.
307) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

308)     Because the payload size is both fixed and a multiple of the cipher
309)     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

310) 
311)     Args:
312)         data:
313)             The encrypted bucket item session key data.
314)         master_keys:
315)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

317) 
318)     Returns:
319)         The bucket item's encryption and signing keys.
320) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

321)     Raises:
322)         cryptography.exceptions.InvalidSignature:
323)             The data does not contain a valid signature under the given
324)             key.
325)         ValueError:
326)             The format is invalid, in a non-cryptographic way.  (For
327)             example, it contains an unsupported version marker, or
328)             unexpected extra contents, or invalid padding.)
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

329) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

330)     Warning:
331)         Non-public function, provided for didactical and educational
332)         purposes only.  Subject to change without notice, including
333)         removal.
334) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

337)     ciphertext, claimed_mac = struct.unpack(
338)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
339)     )
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

340)     actual_mac = hmac.HMAC(master_keys['signing_key'], hashes.SHA256())
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

341)     actual_mac.update(ciphertext)
342)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

343)         _msg.TranslatedString(
344)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_MAC_INFO,
345)             sign_key=_h(master_keys['signing_key']),
346)             ciphertext=_h(ciphertext),
347)             claimed_mac=_h(claimed_mac),
348)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

349)         ),
350)     )
351)     actual_mac.verify(claimed_mac)
352) 
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

353)     try:
354)         iv, payload = struct.unpack(
355)             f'{IV_SIZE}s {len(ciphertext) - IV_SIZE}s', ciphertext
356)         )
357)         decryptor = ciphers.Cipher(
358)             algorithms.AES256(master_keys['encryption_key']), modes.CBC(iv)
359)         ).decryptor()
360)         padded_plaintext = bytearray()
361)         padded_plaintext.extend(decryptor.update(payload))
362)         padded_plaintext.extend(decryptor.finalize())
363)         unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
364)         plaintext = bytearray()
365)         plaintext.extend(unpadder.update(padded_plaintext))
366)         plaintext.extend(unpadder.finalize())
367)         session_encryption_key, session_signing_key = struct.unpack(
368)             f'{KEY_SIZE}s {KEY_SIZE}s', plaintext
369)         )
370)     except (ValueError, struct.error) as exc:
371)         msg = 'Invalid encrypted session keys payload'
372)         raise ValueError(msg) from exc
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

373) 
374)     session_keys: KeyPair = {
375)         'encryption_key': session_encryption_key,
376)         'signing_key': session_signing_key,
377)     }
378) 
379)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

380)         _msg.TranslatedString(
381)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_INFO,
382)             enc_key=_h(master_keys['encryption_key']),
383)             iv=_h(iv),
384)             ciphertext=_h(payload),
385)             plaintext=_h(plaintext),
386)             code=_msg.TranslatedString(
387)                 '{{"encryption_key": bytes.fromhex({enc_key!r}), '
388)                 '"signing_key": bytes.fromhex({sign_key!r})}}',
389)                 enc_key=session_keys['encryption_key'].hex(' '),
390)                 sign_key=session_keys['signing_key'].hex(' '),
391)             ),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

392)         ),
393)     )
394) 
395)     return session_keys
396) 
397) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

398) def decrypt_contents(
399)     data: Buffer,
400)     session_keys: KeyPair,
401) ) -> Buffer:
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

402)     """Decrypt the bucket item's contents.
403) 
404)     The data consists of:
405) 
406)     - a 16-byte IV,
407)     - a variable-sized AES256-CBC-encrypted payload (using PKCS7 padding
408)       on the inside), and
Marco Ricci Add remaining tests to the...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

410) 
411)     The encrypted payload is encrypted with the bucket item's session
412)     encryption key, and the MAC is created with the bucket item's
413)     session signing key.  As per standard cryptographic procedure, the
414)     MAC can be verified before attempting to decrypt the payload.
415) 
416)     Args:
417)         data:
418)             The encrypted bucket item payload data.
419)         session_keys:
420)             The bucket item's session keys.  Presumably these have
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

422)             function.
423) 
424)     Returns:
425)         The bucket item's payload.
426) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

427)     Raises:
428)         cryptography.exceptions.InvalidSignature:
429)             The data does not contain a valid signature under the given
430)             key.
431)         ValueError:
432)             The format is invalid, in a non-cryptographic way.  (For
433)             example, it contains an unsupported version marker, or
434)             unexpected extra contents, or invalid padding.)
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

435) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

436)     Warning:
437)         Non-public function, provided for didactical and educational
438)         purposes only.  Subject to change without notice, including
439)         removal.
440) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

443)     ciphertext, claimed_mac = struct.unpack(
444)         f'{len(data) - MAC_SIZE}s {MAC_SIZE}s', data
445)     )
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

446)     actual_mac = hmac.HMAC(session_keys['signing_key'], hashes.SHA256())
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

447)     actual_mac.update(ciphertext)
448)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

449)         _msg.TranslatedString(
450)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_MAC_INFO,
451)             sign_key=_h(session_keys['signing_key']),
452)             ciphertext=_h(ciphertext),
453)             claimed_mac=_h(claimed_mac),
454)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

455)         ),
456)     )
457)     actual_mac.verify(claimed_mac)
458) 
459)     iv, payload = struct.unpack(
460)         f'{IV_SIZE}s {len(ciphertext) - IV_SIZE}s', ciphertext
461)     )
462)     decryptor = ciphers.Cipher(
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

463)         algorithms.AES256(session_keys['encryption_key']), modes.CBC(iv)
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

464)     ).decryptor()
465)     padded_plaintext = bytearray()
466)     padded_plaintext.extend(decryptor.update(payload))
467)     padded_plaintext.extend(decryptor.finalize())
468)     unpadder = padding.PKCS7(IV_SIZE * 8).unpadder()
469)     plaintext = bytearray()
470)     plaintext.extend(unpadder.update(padded_plaintext))
471)     plaintext.extend(unpadder.finalize())
472) 
473)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

474)         _msg.TranslatedString(
475)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_INFO,
476)             enc_key=_h(session_keys['encryption_key']),
477)             iv=_h(iv),
478)             ciphertext=_h(payload),
479)             plaintext=_h(plaintext),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

480)         ),
481)     )
482) 
483)     return plaintext
484) 
485) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

486) def decrypt_bucket_item(
487)     bucket_item: Buffer,
488)     master_keys: MasterKeys,
489) ) -> Buffer:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

490)     """Decrypt a bucket item.
491) 
492)     Args:
493)         bucket_item:
494)             The encrypted bucket item.
495)         master_keys:
496)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

498) 
499)     Returns:
500)         The decrypted bucket item.
501) 
502)     Raises:
503)         cryptography.exceptions.InvalidSignature:
504)             The data does not contain a valid signature under the given
505)             key.
506)         ValueError:
507)             The format is invalid, in a non-cryptographic way.  (For
508)             example, it contains an unsupported version marker, or
509)             unexpected extra contents, or invalid padding.)
510) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

511)     Warning:
512)         Non-public function, provided for didactical and educational
513)         purposes only.  Subject to change without notice, including
514)         removal.
515) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

519)         _msg.TranslatedString(
520)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_KEY_INFO,
521)             plaintext=_h(bucket_item),
522)             enc_key=_h(master_keys['encryption_key']),
523)             sign_key=_h(master_keys['signing_key']),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

524)         ),
525)     )
526)     data_version, encrypted_session_keys, data_contents = struct.unpack(
527)         (
528)             f'B {ENCRYPTED_KEYPAIR_SIZE}s '
Marco Ricci Add docstrings and better v...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

536)     session_keys = decrypt_session_keys(encrypted_session_keys, master_keys)
537)     return decrypt_contents(data_contents, session_keys)
538) 
539) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

541)     filename: str,
542)     master_keys: MasterKeys,
543)     *,
544)     root_dir: str | bytes | os.PathLike = '.',
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

547) 
548)     Args:
549)         filename:
550)             The bucket file's filename.
551)         master_keys:
552)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

554)         root_dir:
555)             The root directory of the data store.  The filename is
556)             interpreted relatively to this directory.
557) 
558)     Yields:
Marco Ricci Convert old syntax for Yiel...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

560) 
561)     Raises:
562)         cryptography.exceptions.InvalidSignature:
563)             The data does not contain a valid signature under the given
564)             key.
565)         ValueError:
566)             The format is invalid, in a non-cryptographic way.  (For
567)             example, it contains an unsupported version marker, or
568)             unexpected extra contents, or invalid padding.)
569) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

570)     Warning:
571)         Non-public function, provided for didactical and educational
572)         purposes only.  Subject to change without notice, including
573)         removal.
574) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

579)         header_line = bucket_file.readline()
580)         try:
581)             header = json.loads(header_line)
582)         except ValueError as exc:
583)             msg = f'Invalid bucket file: {filename}'
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

591)             )
592) 
593) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

594) 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

595)     """Store the JSON contents at path in the config structure.
596) 
597)     Traverse the config structure according to path, and set the value
598)     of the leaf to the decoded JSON contents.
599) 
600)     A path `/foo/bar/xyz` translates to the JSON structure
601)     `{"foo": {"bar": {"xyz": ...}}}`.
602) 
603)     Args:
604)         config:
605)             The (top-level) configuration structure to update.
606)         path:
607)             The path within the configuration structure to traverse.
608)         json_contents:
609)             The contents to set the item to, after JSON-decoding.
610) 
611)     Raises:
612)         json.JSONDecodeError:
613)             There was an error parsing the JSON contents.
614) 
615)     """
616)     contents = json.loads(json_contents)
617)     path_parts = [part for part in path.split('/') if part]
618)     for part in path_parts[:-1]:
619)         config = config.setdefault(part, {})
620)     if path_parts:
621)         config[path_parts[-1]] = contents
622) 
623) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

626)     path: str | bytes | os.PathLike | None = None,
627)     key: str | Buffer | None = None,
628)     *,
629)     format: str = 'storeroom',  # noqa: A002
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

630) ) -> dict[str, Any]:
631)     """Export the full configuration stored in the storeroom.
632) 
633)     Args:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

634)         path:
635)             The path to the vault configuration directory.  If not
636)             given, then query [`exporter.get_vault_path`][] for the
637)             correct value.
638)         key:
639)             Encryption key/password for the (master keys file in the)
640)             configuration directory, usually the username, or passed via
641)             the `VAULT_KEY` environment variable.  If not given, then
642)             query [`exporter.get_vault_key`][] for the value.
643)         format:
644)             The format to attempt parsing as.  If specified, must be
645)             `storeroom`.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

646) 
647)     Returns:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

650) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

653) 
654)     Raises:
655)         RuntimeError:
656)             Something went wrong during data collection, e.g. we
657)             encountered unsupported or corrupted data in the storeroom.
658)         json.JSONDecodeError:
659)             An internal JSON data structure failed to parse from disk.
660)             The storeroom is probably corrupted.
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

661)         exporter.NotAVaultConfigError:
662)             The directory does contain not a storeroom.
663)         ValueError:
664)             The requested format is invalid.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

665) 
666)     """
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

667)     # Trigger import errors if necessary.
668)     importlib.import_module('cryptography')
669)     if path is None:
670)         path = exporter.get_vault_path()
671)     if key is None:
672)         key = exporter.get_vault_key()
673)     if format != 'storeroom':  # pragma: no cover
674)         msg = exporter.INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT.format(
675)             fmt=format
676)         )
677)         raise ValueError(msg)
678)     try:
679)         master_keys_file = open(  # noqa: SIM115
680)             os.path.join(os.fsdecode(path), '.keys'),
681)             encoding='utf-8',
682)         )
683)     except FileNotFoundError as exc:
684)         raise exporter.NotAVaultConfigError(
685)             os.fsdecode(path),
686)             format='storeroom',
687)         ) from exc
688)     with master_keys_file:
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

689)         header = json.loads(master_keys_file.readline())
690)         if header != {'version': 1}:
691)             msg = 'bad or unsupported keys version header'
692)             raise RuntimeError(msg)
693)         raw_keys_data = base64.standard_b64decode(master_keys_file.readline())
694)         encrypted_keys_params, encrypted_keys = struct.unpack(
695)             f'B {len(raw_keys_data) - 1}s', raw_keys_data
696)         )
697)         if master_keys_file.read():
698)             msg = 'trailing data; cannot make sense of .keys file'
699)             raise RuntimeError(msg)
700)     encrypted_keys_version = encrypted_keys_params >> 4
701)     if encrypted_keys_version != 1:
702)         msg = f'cannot handle version {encrypted_keys_version} encrypted keys'
703)         raise RuntimeError(msg)
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

704)     logger.info(
705)         _msg.TranslatedString(_msg.InfoMsgTemplate.PARSING_MASTER_KEYS_DATA)
706)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

716)     valid_hashdirs = [
717)         hashdir_name
718)         for hashdir_name in os.listdir(storeroom_path_str)
719)         if fnmatch.fnmatch(hashdir_name, '[01][0-9a-f]')
720)     ]
721)     for file in valid_hashdirs:
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

722)         logger.info(
723)             _msg.TranslatedString(
724)                 _msg.InfoMsgTemplate.DECRYPTING_BUCKET,
725)                 bucket_number=file,
726)             )
727)         )
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

728)         bucket_contents = [
729)             bytes(item)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

730)             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

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

Marco Ricci authored 7 months ago

732)         bucket_index = json.loads(bucket_contents.pop(0))
733)         for pos, item in enumerate(bucket_index):
734)             json_contents[item] = bucket_contents[pos]
735)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

736)                 _msg.TranslatedString(
737)                     _msg.DebugMsgTemplate.BUCKET_ITEM_FOUND,
738)                     path=item,
739)                     value=bucket_contents[pos],
740)                 )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

741)             )
742)     dirs_to_check: dict[str, list[str]] = {}
743)     json_payload: Any
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

744)     logger.info(
745)         _msg.TranslatedString(_msg.InfoMsgTemplate.ASSEMBLING_CONFIG_STRUCTURE)
746)     )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

750)                 _msg.TranslatedString(
751)                     _msg.DebugMsgTemplate.POSTPONING_DIRECTORY_CONTENTS_CHECK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

755)             )
756)             json_payload = json.loads(json_content)
757)             if not isinstance(json_payload, list) or any(
758)                 not isinstance(x, str) for x in json_payload
759)             ):
760)                 msg = (
761)                     f'Directory index is not actually an index: '
762)                     f'{json_content!r}'
763)                 )
764)                 raise RuntimeError(msg)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

767)                 _msg.TranslatedString(
768)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS_EMPTY_DIRECTORY,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

773)         else:
774)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

775)                 _msg.TranslatedString(
776)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

782)     logger.info(
783)         _msg.TranslatedString(
784)             _msg.InfoMsgTemplate.CHECKING_CONFIG_STRUCTURE_CONSISTENCY,
785)         )
786)     )
Marco Ricci Emit new info messages and...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

792)             if part:
793)                 # Because we iterate paths in sorted order, parent
794)                 # directories are encountered before child directories.
795)                 # So parent directories always exist (lest we would have
796)                 # aborted earlier).
797)                 #
798)                 # Of course, the type checker doesn't necessarily know
799)                 # this, so we need to use assertions anyway.
800)                 maybe_obj = obj.get(part)
Marco Ricci Update ruff to v0.8.x, refo...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

808)         logger.debug(
809)             _msg.TranslatedString(
810)                 _msg.DebugMsgTemplate.DIRECTORY_CONTENTS_CHECK_OK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

813)             )
814)         )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

816) 
817) 
818) if __name__ == '__main__':
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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