75ea7844415f5c78114860f7958bc39a8e4c6704
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 6 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 1 month 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 1 month 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 6 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 6 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 1 month ago

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

Marco Ricci authored 6 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 6 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 6 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 1 month ago

147)     return _types.StoreroomKeyPair(
148)         encryption_key=encryption_key,
149)         signing_key=signing_key,
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

150)     ).toreadonly()
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 1 month 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 6 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 6 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 6 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 6 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 6 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 Make key pairs, key sets an...

Marco Ricci authored 1 month ago

204)     keys = keys.toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

241)     return _types.StoreroomMasterKeys(
242)         hashing_key=hashing_key,
243)         encryption_key=encryption_key,
244)         signing_key=signing_key,
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

245)     ).toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

289) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

296)     data = memoryview(data).toreadonly().cast('c')
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

297)     master_keys = master_keys.toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 7 months ago

302)     actual_mac.update(ciphertext)
303)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

304)         _msg.TranslatedString(
305)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_MAC_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

334) 
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

335)     session_keys = _types.StoreroomKeyPair(
336)         encryption_key=session_encryption_key,
337)         signing_key=session_signing_key,
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

338)     ).toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

339) 
340)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

341)         _msg.TranslatedString(
342)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_SESSION_KEYS_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

359) def decrypt_contents(
360)     data: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

396) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

403)     data = memoryview(data).toreadonly().cast('c')
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

404)     session_keys = session_keys.toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 7 months ago

409)     actual_mac.update(ciphertext)
410)     logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

411)         _msg.TranslatedString(
412)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_MAC_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

414)             ciphertext=_h(ciphertext),
415)             claimed_mac=_h(claimed_mac),
416)             actual_mac=_h(actual_mac.copy().finalize()),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

436)         _msg.TranslatedString(
437)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_INFO,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

439)             iv=_h(iv),
440)             ciphertext=_h(payload),
441)             plaintext=_h(plaintext),
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

442)         ),
443)     )
444) 
445)     return plaintext
446) 
447) 
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

448) def decrypt_bucket_item(
449)     bucket_item: Buffer,
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

479)     bucket_item = memoryview(bucket_item).toreadonly().cast('c')
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

480)     master_keys = master_keys.toreadonly()
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

482)         _msg.TranslatedString(
483)             _msg.DebugMsgTemplate.DECRYPT_BUCKET_ITEM_KEY_INFO,
484)             plaintext=_h(bucket_item),
Marco Ricci Move storeroom helper types...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 7 months ago

487)         ),
488)     )
489)     data_version, encrypted_session_keys, data_contents = struct.unpack(
490)         (
491)             f'B {ENCRYPTED_KEYPAIR_SIZE}s '
Marco Ricci Add docstrings and better v...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

499)     session_keys = decrypt_session_keys(encrypted_session_keys, master_keys)
500)     return decrypt_contents(data_contents, session_keys)
501) 
502) 
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

510) 
511)     Args:
512)         filename:
513)             The bucket file's filename.
514)         master_keys:
515)             The master keys.  Presumably these have previously been
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

517)         root_dir:
518)             The root directory of the data store.  The filename is
519)             interpreted relatively to this directory.
520) 
521)     Yields:
Marco Ricci Convert old syntax for Yiel...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

533)     Warning:
534)         Non-public function, provided for didactical and educational
535)         purposes only.  Subject to change without notice, including
536)         removal.
537) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

538)     """
Marco Ricci Make key pairs, key sets an...

Marco Ricci authored 1 month ago

539)     master_keys = master_keys.toreadonly()
Marco Ricci Support exports from outsid...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

543)         header_line = bucket_file.readline()
544)         try:
545)             header = json.loads(header_line)
546)         except ValueError as exc:
547)             msg = f'Invalid bucket file: {filename}'
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

555)             )
556) 
557) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

588) @exporter.register_export_vault_config_data_handler('storeroom')
Marco Ricci Consolidate ExportVaultConf...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

590)     path: str | bytes | os.PathLike | None = None,
591)     key: str | Buffer | None = None,
592)     *,
593)     format: str = 'storeroom',  # noqa: A002
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

594) ) -> dict[str, Any]:
595)     """Export the full configuration stored in the storeroom.
596) 
Marco Ricci Consolidate ExportVaultConf...

Marco Ricci authored 1 month ago

597)     See [`exporter.ExportVaultConfigDataFunction`][] for an explanation
598)     of the call signature, and the exceptions to expect.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

599) 
Marco Ricci Consolidate ExportVaultConf...

Marco Ricci authored 1 month ago

600)     Other Args:
601)         format:
602)             The only supported format is `storeroom`.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

603) 
Marco Ricci Consolidate ExportVaultConf...

Marco Ricci authored 1 month ago

604)     """  # noqa: DOC201,DOC501
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

605)     # Trigger import errors if necessary.
606)     importlib.import_module('cryptography')
607)     if path is None:
608)         path = exporter.get_vault_path()
609)     if key is None:
610)         key = exporter.get_vault_key()
611)     if format != 'storeroom':  # pragma: no cover
612)         msg = exporter.INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT.format(
613)             fmt=format
614)         )
615)         raise ValueError(msg)
616)     try:
617)         master_keys_file = open(  # noqa: SIM115
618)             os.path.join(os.fsdecode(path), '.keys'),
619)             encoding='utf-8',
620)         )
621)     except FileNotFoundError as exc:
622)         raise exporter.NotAVaultConfigError(
623)             os.fsdecode(path),
624)             format='storeroom',
625)         ) from exc
626)     with master_keys_file:
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

627)         header = json.loads(master_keys_file.readline())
628)         if header != {'version': 1}:
629)             msg = 'bad or unsupported keys version header'
630)             raise RuntimeError(msg)
631)         raw_keys_data = base64.standard_b64decode(master_keys_file.readline())
632)         encrypted_keys_params, encrypted_keys = struct.unpack(
633)             f'B {len(raw_keys_data) - 1}s', raw_keys_data
634)         )
635)         if master_keys_file.read():
636)             msg = 'trailing data; cannot make sense of .keys file'
637)             raise RuntimeError(msg)
638)     encrypted_keys_version = encrypted_keys_params >> 4
639)     if encrypted_keys_version != 1:
640)         msg = f'cannot handle version {encrypted_keys_version} encrypted keys'
641)         raise RuntimeError(msg)
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

642)     logger.info(
643)         _msg.TranslatedString(_msg.InfoMsgTemplate.PARSING_MASTER_KEYS_DATA)
644)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

654)     valid_hashdirs = [
655)         hashdir_name
656)         for hashdir_name in os.listdir(storeroom_path_str)
657)         if fnmatch.fnmatch(hashdir_name, '[01][0-9a-f]')
658)     ]
659)     for file in valid_hashdirs:
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

660)         logger.info(
661)             _msg.TranslatedString(
662)                 _msg.InfoMsgTemplate.DECRYPTING_BUCKET,
663)                 bucket_number=file,
664)             )
665)         )
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

666)         bucket_contents = [
667)             bytes(item)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

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

Marco Ricci authored 6 months ago

670)         bucket_index = json.loads(bucket_contents.pop(0))
671)         for pos, item in enumerate(bucket_index):
672)             json_contents[item] = bucket_contents[pos]
673)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

674)                 _msg.TranslatedString(
675)                     _msg.DebugMsgTemplate.BUCKET_ITEM_FOUND,
676)                     path=item,
677)                     value=bucket_contents[pos],
678)                 )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

679)             )
680)     dirs_to_check: dict[str, list[str]] = {}
681)     json_payload: Any
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

682)     logger.info(
683)         _msg.TranslatedString(_msg.InfoMsgTemplate.ASSEMBLING_CONFIG_STRUCTURE)
684)     )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

688)                 _msg.TranslatedString(
689)                     _msg.DebugMsgTemplate.POSTPONING_DIRECTORY_CONTENTS_CHECK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

693)             )
694)             json_payload = json.loads(json_content)
695)             if not isinstance(json_payload, list) or any(
696)                 not isinstance(x, str) for x in json_payload
697)             ):
698)                 msg = (
699)                     f'Directory index is not actually an index: '
700)                     f'{json_content!r}'
701)                 )
702)                 raise RuntimeError(msg)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

705)                 _msg.TranslatedString(
706)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS_EMPTY_DIRECTORY,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

711)         else:
712)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

713)                 _msg.TranslatedString(
714)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

720)     logger.info(
721)         _msg.TranslatedString(
722)             _msg.InfoMsgTemplate.CHECKING_CONFIG_STRUCTURE_CONSISTENCY,
723)         )
724)     )
Marco Ricci Emit new info messages and...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

730)             if part:
731)                 # Because we iterate paths in sorted order, parent
732)                 # directories are encountered before child directories.
733)                 # So parent directories always exist (lest we would have
734)                 # aborted earlier).
735)                 #
736)                 # Of course, the type checker doesn't necessarily know
737)                 # this, so we need to use assertions anyway.
738)                 maybe_obj = obj.get(part)
Marco Ricci Update ruff to v0.8.x, refo...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

746)         logger.debug(
747)             _msg.TranslatedString(
748)                 _msg.DebugMsgTemplate.DIRECTORY_CONTENTS_CHECK_OK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

751)             )
752)         )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

754) 
755) 
756) if __name__ == '__main__':
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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