d4d62b2708b2895095ff9e2ca71f2d370755d156
Marco Ricci Update copyright notices to...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

4) 
Marco Ricci Add vault_native exporter f...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

22) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

27) import base64
Marco Ricci Add support for Python 3.9

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months 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 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 7 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 7 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 7 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 7 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 7 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 7 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 7 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 7 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 7 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 2 months 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 7 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 7 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 7 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 2 months 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 2 months 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 7 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 7 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 Apply new ruff ruleset to c...

Marco Ricci authored 6 months ago

589) def export_storeroom_data(  # noqa: C901,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 7 months ago

594) ) -> dict[str, Any]:
595)     """Export the full configuration stored in the storeroom.
596) 
597)     Args:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

610) 
611)     Returns:
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

614) 
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

625)         exporter.NotAVaultConfigError:
626)             The directory does contain not a storeroom.
627)         ValueError:
628)             The requested format is invalid.
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

629) 
630)     """
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

668)     logger.info(
669)         _msg.TranslatedString(_msg.InfoMsgTemplate.PARSING_MASTER_KEYS_DATA)
670)     )
Marco Ricci Add prototype for "storeroo...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

680)     valid_hashdirs = [
681)         hashdir_name
682)         for hashdir_name in os.listdir(storeroom_path_str)
683)         if fnmatch.fnmatch(hashdir_name, '[01][0-9a-f]')
684)     ]
685)     for file in valid_hashdirs:
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

686)         logger.info(
687)             _msg.TranslatedString(
688)                 _msg.InfoMsgTemplate.DECRYPTING_BUCKET,
689)                 bucket_number=file,
690)             )
691)         )
Marco Ricci Accept all bytes-like objec...

Marco Ricci authored 2 months ago

692)         bucket_contents = [
693)             bytes(item)
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

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

Marco Ricci authored 7 months ago

696)         bucket_index = json.loads(bucket_contents.pop(0))
697)         for pos, item in enumerate(bucket_index):
698)             json_contents[item] = bucket_contents[pos]
699)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

700)                 _msg.TranslatedString(
701)                     _msg.DebugMsgTemplate.BUCKET_ITEM_FOUND,
702)                     path=item,
703)                     value=bucket_contents[pos],
704)                 )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

705)             )
706)     dirs_to_check: dict[str, list[str]] = {}
707)     json_payload: Any
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

708)     logger.info(
709)         _msg.TranslatedString(_msg.InfoMsgTemplate.ASSEMBLING_CONFIG_STRUCTURE)
710)     )
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

714)                 _msg.TranslatedString(
715)                     _msg.DebugMsgTemplate.POSTPONING_DIRECTORY_CONTENTS_CHECK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

731)                 _msg.TranslatedString(
732)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS_EMPTY_DIRECTORY,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

737)         else:
738)             logger.debug(
Marco Ricci Make debug and info message...

Marco Ricci authored 2 months ago

739)                 _msg.TranslatedString(
740)                     _msg.DebugMsgTemplate.SETTING_CONFIG_STRUCTURE_CONTENTS,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

746)     logger.info(
747)         _msg.TranslatedString(
748)             _msg.InfoMsgTemplate.CHECKING_CONFIG_STRUCTURE_CONSISTENCY,
749)         )
750)     )
Marco Ricci Emit new info messages and...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

772)         logger.debug(
773)             _msg.TranslatedString(
774)                 _msg.DebugMsgTemplate.DIRECTORY_CONTENTS_CHECK_OK,
Marco Ricci Harmonize the interface for...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 2 months ago

777)             )
778)         )
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 7 months ago

780) 
781) 
782) if __name__ == '__main__':
Marco Ricci Add an actual storeroom exp...

Marco Ricci authored 7 months ago

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

Marco Ricci authored 2 months ago

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